blob: 710ba57630218a937474fc29fb4a87dbd4f0e328 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.cf.direct;
import com.android.dx.cf.attrib.AttAnnotationDefault;
import com.android.dx.cf.attrib.AttCode;
import com.android.dx.cf.attrib.AttConstantValue;
import com.android.dx.cf.attrib.AttDeprecated;
import com.android.dx.cf.attrib.AttEnclosingMethod;
import com.android.dx.cf.attrib.AttExceptions;
import com.android.dx.cf.attrib.AttInnerClasses;
import com.android.dx.cf.attrib.AttLineNumberTable;
import com.android.dx.cf.attrib.AttLocalVariableTable;
import com.android.dx.cf.attrib.AttLocalVariableTypeTable;
import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations;
import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations;
import com.android.dx.cf.attrib.AttSignature;
import com.android.dx.cf.attrib.AttSourceDebugExtension;
import com.android.dx.cf.attrib.AttSourceFile;
import com.android.dx.cf.attrib.AttSynthetic;
import com.android.dx.cf.attrib.InnerClassList;
import com.android.dx.cf.code.ByteCatchList;
import com.android.dx.cf.code.BytecodeArray;
import com.android.dx.cf.code.LineNumberList;
import com.android.dx.cf.code.LocalVariableList;
import com.android.dx.cf.iface.Attribute;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.cf.iface.ParseObserver;
import com.android.dx.cf.iface.StdAttributeList;
import com.android.dx.rop.annotation.AnnotationVisibility;
import com.android.dx.rop.annotation.Annotations;
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.code.AccessFlags;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.ConstantPool;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.cst.TypedConstant;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;
import java.io.IOException;
/**
* Standard subclass of {@link AttributeFactory}, which knows how to parse
* all the standard attribute types.
*/
public class StdAttributeFactory
extends AttributeFactory {
/** {@code non-null;} shared instance of this class */
public static final StdAttributeFactory THE_ONE =
new StdAttributeFactory();
/**
* Constructs an instance.
*/
public StdAttributeFactory() {
// This space intentionally left blank.
}
/** {@inheritDoc} */
@Override
protected Attribute parse0(DirectClassFile cf, int context, String name,
int offset, int length, ParseObserver observer) {
switch (context) {
case CTX_CLASS: {
if (name == AttDeprecated.ATTRIBUTE_NAME) {
return deprecated(cf, offset, length, observer);
}
if (name == AttEnclosingMethod.ATTRIBUTE_NAME) {
return enclosingMethod(cf, offset, length, observer);
}
if (name == AttInnerClasses.ATTRIBUTE_NAME) {
return innerClasses(cf, offset, length, observer);
}
if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeInvisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeVisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttSynthetic.ATTRIBUTE_NAME) {
return synthetic(cf, offset, length, observer);
}
if (name == AttSignature.ATTRIBUTE_NAME) {
return signature(cf, offset, length, observer);
}
if (name == AttSourceDebugExtension.ATTRIBUTE_NAME) {
return sourceDebugExtension(cf, offset, length, observer);
}
if (name == AttSourceFile.ATTRIBUTE_NAME) {
return sourceFile(cf, offset, length, observer);
}
break;
}
case CTX_FIELD: {
if (name == AttConstantValue.ATTRIBUTE_NAME) {
return constantValue(cf, offset, length, observer);
}
if (name == AttDeprecated.ATTRIBUTE_NAME) {
return deprecated(cf, offset, length, observer);
}
if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeInvisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeVisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttSignature.ATTRIBUTE_NAME) {
return signature(cf, offset, length, observer);
}
if (name == AttSynthetic.ATTRIBUTE_NAME) {
return synthetic(cf, offset, length, observer);
}
break;
}
case CTX_METHOD: {
if (name == AttAnnotationDefault.ATTRIBUTE_NAME) {
return annotationDefault(cf, offset, length, observer);
}
if (name == AttCode.ATTRIBUTE_NAME) {
return code(cf, offset, length, observer);
}
if (name == AttDeprecated.ATTRIBUTE_NAME) {
return deprecated(cf, offset, length, observer);
}
if (name == AttExceptions.ATTRIBUTE_NAME) {
return exceptions(cf, offset, length, observer);
}
if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeInvisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) {
return runtimeVisibleAnnotations(cf, offset, length,
observer);
}
if (name == AttRuntimeInvisibleParameterAnnotations.
ATTRIBUTE_NAME) {
return runtimeInvisibleParameterAnnotations(
cf, offset, length, observer);
}
if (name == AttRuntimeVisibleParameterAnnotations.
ATTRIBUTE_NAME) {
return runtimeVisibleParameterAnnotations(
cf, offset, length, observer);
}
if (name == AttSignature.ATTRIBUTE_NAME) {
return signature(cf, offset, length, observer);
}
if (name == AttSynthetic.ATTRIBUTE_NAME) {
return synthetic(cf, offset, length, observer);
}
break;
}
case CTX_CODE: {
if (name == AttLineNumberTable.ATTRIBUTE_NAME) {
return lineNumberTable(cf, offset, length, observer);
}
if (name == AttLocalVariableTable.ATTRIBUTE_NAME) {
return localVariableTable(cf, offset, length, observer);
}
if (name == AttLocalVariableTypeTable.ATTRIBUTE_NAME) {
return localVariableTypeTable(cf, offset, length,
observer);
}
break;
}
}
return super.parse0(cf, context, name, offset, length, observer);
}
/**
* Parses an {@code AnnotationDefault} attribute.
*/
private Attribute annotationDefault(DirectClassFile cf,
int offset, int length, ParseObserver observer) {
if (length < 2) {
throwSeverelyTruncated();
}
AnnotationParser ap =
new AnnotationParser(cf, offset, length, observer);
Constant cst = ap.parseValueAttribute();
return new AttAnnotationDefault(cst, length);
}
/**
* Parses a {@code Code} attribute.
*/
private Attribute code(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length < 12) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int maxStack = bytes.getUnsignedShort(offset); // u2 max_stack
int maxLocals = bytes.getUnsignedShort(offset + 2); // u2 max_locals
int codeLength = bytes.getInt(offset + 4); // u4 code_length
int origOffset = offset;
if (observer != null) {
observer.parsed(bytes, offset, 2,
"max_stack: " + Hex.u2(maxStack));
observer.parsed(bytes, offset + 2, 2,
"max_locals: " + Hex.u2(maxLocals));
observer.parsed(bytes, offset + 4, 4,
"code_length: " + Hex.u4(codeLength));
}
offset += 8;
length -= 8;
if (length < (codeLength + 4)) {
return throwTruncated();
}
int codeOffset = offset;
offset += codeLength;
length -= codeLength;
BytecodeArray code =
new BytecodeArray(bytes.slice(codeOffset, codeOffset + codeLength),
pool);
if (observer != null) {
code.forEach(new CodeObserver(code.getBytes(), observer));
}
// u2 exception_table_length
int exceptionTableLength = bytes.getUnsignedShort(offset);
ByteCatchList catches = (exceptionTableLength == 0) ?
ByteCatchList.EMPTY :
new ByteCatchList(exceptionTableLength);
if (observer != null) {
observer.parsed(bytes, offset, 2,
"exception_table_length: " +
Hex.u2(exceptionTableLength));
}
offset += 2;
length -= 2;
if (length < (exceptionTableLength * 8 + 2)) {
return throwTruncated();
}
for (int i = 0; i < exceptionTableLength; i++) {
if (observer != null) {
observer.changeIndent(1);
}
int startPc = bytes.getUnsignedShort(offset);
int endPc = bytes.getUnsignedShort(offset + 2);
int handlerPc = bytes.getUnsignedShort(offset + 4);
int catchTypeIdx = bytes.getUnsignedShort(offset + 6);
CstType catchType = (CstType) pool.get0Ok(catchTypeIdx);
catches.set(i, startPc, endPc, handlerPc, catchType);
if (observer != null) {
observer.parsed(bytes, offset, 8,
Hex.u2(startPc) + ".." + Hex.u2(endPc) +
" -> " + Hex.u2(handlerPc) + " " +
((catchType == null) ? "<any>" :
catchType.toHuman()));
}
offset += 8;
length -= 8;
if (observer != null) {
observer.changeIndent(-1);
}
}
catches.setImmutable();
AttributeListParser parser =
new AttributeListParser(cf, CTX_CODE, offset, this);
parser.setObserver(observer);
StdAttributeList attributes = parser.getList();
attributes.setImmutable();
int attributeByteCount = parser.getEndOffset() - offset;
if (attributeByteCount != length) {
return throwBadLength(attributeByteCount + (offset - origOffset));
}
return new AttCode(maxStack, maxLocals, code, catches, attributes);
}
/**
* Parses a {@code ConstantValue} attribute.
*/
private Attribute constantValue(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length != 2) {
return throwBadLength(2);
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int idx = bytes.getUnsignedShort(offset);
TypedConstant cst = (TypedConstant) pool.get(idx);
Attribute result = new AttConstantValue(cst);
if (observer != null) {
observer.parsed(bytes, offset, 2, "value: " + cst);
}
return result;
}
/**
* Parses a {@code Deprecated} attribute.
*/
private Attribute deprecated(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length != 0) {
return throwBadLength(0);
}
return new AttDeprecated();
}
/**
* Parses an {@code EnclosingMethod} attribute.
*/
private Attribute enclosingMethod(DirectClassFile cf, int offset,
int length, ParseObserver observer) {
if (length != 4) {
throwBadLength(4);
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int idx = bytes.getUnsignedShort(offset);
CstType type = (CstType) pool.get(idx);
idx = bytes.getUnsignedShort(offset + 2);
CstNat method = (CstNat) pool.get0Ok(idx);
Attribute result = new AttEnclosingMethod(type, method);
if (observer != null) {
observer.parsed(bytes, offset, 2, "class: " + type);
observer.parsed(bytes, offset + 2, 2, "method: " +
DirectClassFile.stringOrNone(method));
}
return result;
}
/**
* Parses an {@code Exceptions} attribute.
*/
private Attribute exceptions(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length < 2) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
int count = bytes.getUnsignedShort(offset); // number_of_exceptions
if (observer != null) {
observer.parsed(bytes, offset, 2,
"number_of_exceptions: " + Hex.u2(count));
}
offset += 2;
length -= 2;
if (length != (count * 2)) {
throwBadLength((count * 2) + 2);
}
TypeList list = cf.makeTypeList(offset, count);
return new AttExceptions(list);
}
/**
* Parses an {@code InnerClasses} attribute.
*/
private Attribute innerClasses(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length < 2) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int count = bytes.getUnsignedShort(offset); // number_of_classes
if (observer != null) {
observer.parsed(bytes, offset, 2,
"number_of_classes: " + Hex.u2(count));
}
offset += 2;
length -= 2;
if (length != (count * 8)) {
throwBadLength((count * 8) + 2);
}
InnerClassList list = new InnerClassList(count);
for (int i = 0; i < count; i++) {
int innerClassIdx = bytes.getUnsignedShort(offset);
int outerClassIdx = bytes.getUnsignedShort(offset + 2);
int nameIdx = bytes.getUnsignedShort(offset + 4);
int accessFlags = bytes.getUnsignedShort(offset + 6);
CstType innerClass = (CstType) pool.get(innerClassIdx);
CstType outerClass = (CstType) pool.get0Ok(outerClassIdx);
CstString name = (CstString) pool.get0Ok(nameIdx);
list.set(i, innerClass, outerClass, name, accessFlags);
if (observer != null) {
observer.parsed(bytes, offset, 2,
"inner_class: " +
DirectClassFile.stringOrNone(innerClass));
observer.parsed(bytes, offset + 2, 2,
" outer_class: " +
DirectClassFile.stringOrNone(outerClass));
observer.parsed(bytes, offset + 4, 2,
" name: " +
DirectClassFile.stringOrNone(name));
observer.parsed(bytes, offset + 6, 2,
" access_flags: " +
AccessFlags.innerClassString(accessFlags));
}
offset += 8;
}
list.setImmutable();
return new AttInnerClasses(list);
}
/**
* Parses a {@code LineNumberTable} attribute.
*/
private Attribute lineNumberTable(DirectClassFile cf, int offset,
int length, ParseObserver observer) {
if (length < 2) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
int count = bytes.getUnsignedShort(offset); // line_number_table_length
if (observer != null) {
observer.parsed(bytes, offset, 2,
"line_number_table_length: " + Hex.u2(count));
}
offset += 2;
length -= 2;
if (length != (count * 4)) {
throwBadLength((count * 4) + 2);
}
LineNumberList list = new LineNumberList(count);
for (int i = 0; i < count; i++) {
int startPc = bytes.getUnsignedShort(offset);
int lineNumber = bytes.getUnsignedShort(offset + 2);
list.set(i, startPc, lineNumber);
if (observer != null) {
observer.parsed(bytes, offset, 4,
Hex.u2(startPc) + " " + lineNumber);
}
offset += 4;
}
list.setImmutable();
return new AttLineNumberTable(list);
}
/**
* Parses a {@code LocalVariableTable} attribute.
*/
private Attribute localVariableTable(DirectClassFile cf, int offset,
int length, ParseObserver observer) {
if (length < 2) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
int count = bytes.getUnsignedShort(offset);
if (observer != null) {
observer.parsed(bytes, offset, 2,
"local_variable_table_length: " + Hex.u2(count));
}
LocalVariableList list = parseLocalVariables(
bytes.slice(offset + 2, offset + length), cf.getConstantPool(),
observer, count, false);
return new AttLocalVariableTable(list);
}
/**
* Parses a {@code LocalVariableTypeTable} attribute.
*/
private Attribute localVariableTypeTable(DirectClassFile cf, int offset,
int length, ParseObserver observer) {
if (length < 2) {
return throwSeverelyTruncated();
}
ByteArray bytes = cf.getBytes();
int count = bytes.getUnsignedShort(offset);
if (observer != null) {
observer.parsed(bytes, offset, 2,
"local_variable_type_table_length: " + Hex.u2(count));
}
LocalVariableList list = parseLocalVariables(
bytes.slice(offset + 2, offset + length), cf.getConstantPool(),
observer, count, true);
return new AttLocalVariableTypeTable(list);
}
/**
* Parse the table part of either a {@code LocalVariableTable}
* or a {@code LocalVariableTypeTable}.
*
* @param bytes {@code non-null;} bytes to parse, which should <i>only</i>
* contain the table data (no header)
* @param pool {@code non-null;} constant pool to use
* @param count {@code >= 0;} the number of entries
* @param typeTable {@code true} iff this is for a type table
* @return {@code non-null;} the constructed list
*/
private LocalVariableList parseLocalVariables(ByteArray bytes,
ConstantPool pool, ParseObserver observer, int count,
boolean typeTable) {
if (bytes.size() != (count * 10)) {
// "+ 2" is for the count.
throwBadLength((count * 10) + 2);
}
ByteArray.MyDataInputStream in = bytes.makeDataInputStream();
LocalVariableList list = new LocalVariableList(count);
try {
for (int i = 0; i < count; i++) {
int startPc = in.readUnsignedShort();
int length = in.readUnsignedShort();
int nameIdx = in.readUnsignedShort();
int typeIdx = in.readUnsignedShort();
int index = in.readUnsignedShort();
CstString name = (CstString) pool.get(nameIdx);
CstString type = (CstString) pool.get(typeIdx);
CstString descriptor = null;
CstString signature = null;
if (typeTable) {
signature = type;
} else {
descriptor = type;
}
list.set(i, startPc, length, name,
descriptor, signature, index);
if (observer != null) {
observer.parsed(bytes, i * 10, 10, Hex.u2(startPc) +
".." + Hex.u2(startPc + length) + " " +
Hex.u2(index) + " " + name.toHuman() + " " +
type.toHuman());
}
}
} catch (IOException ex) {
throw new RuntimeException("shouldn't happen", ex);
}
list.setImmutable();
return list;
}
/**
* Parses a {@code RuntimeInvisibleAnnotations} attribute.
*/
private Attribute runtimeInvisibleAnnotations(DirectClassFile cf,
int offset, int length, ParseObserver observer) {
if (length < 2) {
throwSeverelyTruncated();
}
AnnotationParser ap =
new AnnotationParser(cf, offset, length, observer);
Annotations annotations =
ap.parseAnnotationAttribute(AnnotationVisibility.BUILD);
return new AttRuntimeInvisibleAnnotations(annotations, length);
}
/**
* Parses a {@code RuntimeVisibleAnnotations} attribute.
*/
private Attribute runtimeVisibleAnnotations(DirectClassFile cf,
int offset, int length, ParseObserver observer) {
if (length < 2) {
throwSeverelyTruncated();
}
AnnotationParser ap =
new AnnotationParser(cf, offset, length, observer);
Annotations annotations =
ap.parseAnnotationAttribute(AnnotationVisibility.RUNTIME);
return new AttRuntimeVisibleAnnotations(annotations, length);
}
/**
* Parses a {@code RuntimeInvisibleParameterAnnotations} attribute.
*/
private Attribute runtimeInvisibleParameterAnnotations(DirectClassFile cf,
int offset, int length, ParseObserver observer) {
if (length < 2) {
throwSeverelyTruncated();
}
AnnotationParser ap =
new AnnotationParser(cf, offset, length, observer);
AnnotationsList list =
ap.parseParameterAttribute(AnnotationVisibility.BUILD);
return new AttRuntimeInvisibleParameterAnnotations(list, length);
}
/**
* Parses a {@code RuntimeVisibleParameterAnnotations} attribute.
*/
private Attribute runtimeVisibleParameterAnnotations(DirectClassFile cf,
int offset, int length, ParseObserver observer) {
if (length < 2) {
throwSeverelyTruncated();
}
AnnotationParser ap =
new AnnotationParser(cf, offset, length, observer);
AnnotationsList list =
ap.parseParameterAttribute(AnnotationVisibility.RUNTIME);
return new AttRuntimeVisibleParameterAnnotations(list, length);
}
/**
* Parses a {@code Signature} attribute.
*/
private Attribute signature(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length != 2) {
throwBadLength(2);
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int idx = bytes.getUnsignedShort(offset);
CstString cst = (CstString) pool.get(idx);
Attribute result = new AttSignature(cst);
if (observer != null) {
observer.parsed(bytes, offset, 2, "signature: " + cst);
}
return result;
}
/**
* Parses a {@code SourceDebugExtesion} attribute.
*/
private Attribute sourceDebugExtension(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
ByteArray bytes = cf.getBytes().slice(offset, offset + length);
CstString smapString = new CstString(bytes);
Attribute result = new AttSourceDebugExtension(smapString);
if (observer != null) {
String decoded = smapString.getString();
observer.parsed(bytes, offset, length, "sourceDebugExtension: " + decoded);
}
return result;
}
/**
* Parses a {@code SourceFile} attribute.
*/
private Attribute sourceFile(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length != 2) {
throwBadLength(2);
}
ByteArray bytes = cf.getBytes();
ConstantPool pool = cf.getConstantPool();
int idx = bytes.getUnsignedShort(offset);
CstString cst = (CstString) pool.get(idx);
Attribute result = new AttSourceFile(cst);
if (observer != null) {
observer.parsed(bytes, offset, 2, "source: " + cst);
}
return result;
}
/**
* Parses a {@code Synthetic} attribute.
*/
private Attribute synthetic(DirectClassFile cf, int offset, int length,
ParseObserver observer) {
if (length != 0) {
return throwBadLength(0);
}
return new AttSynthetic();
}
/**
* Throws the right exception when a known attribute has a way too short
* length.
*
* @return never
* @throws ParseException always thrown
*/
private static Attribute throwSeverelyTruncated() {
throw new ParseException("severely truncated attribute");
}
/**
* Throws the right exception when a known attribute has a too short
* length.
*
* @return never
* @throws ParseException always thrown
*/
private static Attribute throwTruncated() {
throw new ParseException("truncated attribute");
}
/**
* Throws the right exception when an attribute has an unexpected length
* (given its contents).
*
* @param expected expected length
* @return never
* @throws ParseException always thrown
*/
private static Attribute throwBadLength(int expected) {
throw new ParseException("bad attribute length; expected length " +
Hex.u4(expected));
}
}