blob: f908547cb5bcc0ab5ab5d3d4dbae2c4bf58d420f [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.AttSourceFile;
import com.android.dx.cf.cst.ConstantPoolParser;
import com.android.dx.cf.iface.Attribute;
import com.android.dx.cf.iface.AttributeList;
import com.android.dx.cf.iface.ClassFile;
import com.android.dx.cf.iface.FieldList;
import com.android.dx.cf.iface.MethodList;
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.code.AccessFlags;
import com.android.dx.rop.cst.ConstantPool;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.cst.StdConstantPool;
import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.ByteArray;
import com.android.dx.util.Hex;
/**
* Class file with info taken from a {@code byte[]} or slice thereof.
*/
public class DirectClassFile implements ClassFile {
/** the expected value of the ClassFile.magic field */
private static final int CLASS_FILE_MAGIC = 0xcafebabe;
/**
* minimum {@code .class} file major version
*
* See http://en.wikipedia.org/wiki/Java_class_file for an up-to-date
* list of version numbers. Currently known (taken from that table) are:
*
* J2SE 7.0 = 51 (0x33 hex),
* J2SE 6.0 = 50 (0x32 hex),
* J2SE 5.0 = 49 (0x31 hex),
* JDK 1.4 = 48 (0x30 hex),
* JDK 1.3 = 47 (0x2F hex),
* JDK 1.2 = 46 (0x2E hex),
* JDK 1.1 = 45 (0x2D hex).
*
* Valid ranges are typically of the form
* "A.0 through B.C inclusive" where A <= B and C >= 0,
* which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
*/
private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45;
/**
* maximum {@code .class} file major version
*
* Note: if you change this, please change "java.class.version" in System.java.
*/
private static final int CLASS_FILE_MAX_MAJOR_VERSION = 51;
/** maximum {@code .class} file minor version */
private static final int CLASS_FILE_MAX_MINOR_VERSION = 0;
/**
* {@code non-null;} the file path for the class, excluding any base directory
* specification
*/
private final String filePath;
/** {@code non-null;} the bytes of the file */
private final ByteArray bytes;
/**
* whether to be strict about parsing; if
* {@code false}, this avoids doing checks that only exist
* for purposes of verification (such as magic number matching and
* path-package consistency checking)
*/
private final boolean strictParse;
/**
* {@code null-ok;} the constant pool; only ever {@code null}
* before the constant pool is successfully parsed
*/
private StdConstantPool pool;
/**
* the class file field {@code access_flags}; will be {@code -1}
* before the file is successfully parsed
*/
private int accessFlags;
/**
* {@code null-ok;} the class file field {@code this_class},
* interpreted as a type constant; only ever {@code null}
* before the file is successfully parsed
*/
private CstType thisClass;
/**
* {@code null-ok;} the class file field {@code super_class}, interpreted
* as a type constant if non-zero
*/
private CstType superClass;
/**
* {@code null-ok;} the class file field {@code interfaces}; only
* ever {@code null} before the file is successfully
* parsed
*/
private TypeList interfaces;
/**
* {@code null-ok;} the class file field {@code fields}; only ever
* {@code null} before the file is successfully parsed
*/
private FieldList fields;
/**
* {@code null-ok;} the class file field {@code methods}; only ever
* {@code null} before the file is successfully parsed
*/
private MethodList methods;
/**
* {@code null-ok;} the class file field {@code attributes}; only
* ever {@code null} before the file is successfully
* parsed
*/
private StdAttributeList attributes;
/** {@code null-ok;} attribute factory, if any */
private AttributeFactory attributeFactory;
/** {@code null-ok;} parse observer, if any */
private ParseObserver observer;
/**
* Returns the string form of an object or {@code "(none)"}
* (rather than {@code "null"}) for {@code null}.
*
* @param obj {@code null-ok;} the object to stringify
* @return {@code non-null;} the appropriate string form
*/
public static String stringOrNone(Object obj) {
if (obj == null) {
return "(none)";
}
return obj.toString();
}
/**
* Constructs an instance.
*
* @param bytes {@code non-null;} the bytes of the file
* @param filePath {@code non-null;} the file path for the class,
* excluding any base directory specification
* @param strictParse whether to be strict about parsing; if
* {@code false}, this avoids doing checks that only exist
* for purposes of verification (such as magic number matching and
* path-package consistency checking)
*/
public DirectClassFile(ByteArray bytes, String filePath,
boolean strictParse) {
if (bytes == null) {
throw new NullPointerException("bytes == null");
}
if (filePath == null) {
throw new NullPointerException("filePath == null");
}
this.filePath = filePath;
this.bytes = bytes;
this.strictParse = strictParse;
this.accessFlags = -1;
}
/**
* Constructs an instance.
*
* @param bytes {@code non-null;} the bytes of the file
* @param filePath {@code non-null;} the file path for the class,
* excluding any base directory specification
* @param strictParse whether to be strict about parsing; if
* {@code false}, this avoids doing checks that only exist
* for purposes of verification (such as magic number matching and
* path-package consistency checking)
*/
public DirectClassFile(byte[] bytes, String filePath,
boolean strictParse) {
this(new ByteArray(bytes), filePath, strictParse);
}
/**
* Sets the parse observer for this instance.
*
* @param observer {@code null-ok;} the observer
*/
public void setObserver(ParseObserver observer) {
this.observer = observer;
}
/**
* Sets the attribute factory to use.
*
* @param attributeFactory {@code non-null;} the attribute factory
*/
public void setAttributeFactory(AttributeFactory attributeFactory) {
if (attributeFactory == null) {
throw new NullPointerException("attributeFactory == null");
}
this.attributeFactory = attributeFactory;
}
/**
* Gets the path where this class file is located.
*
* @return {@code non-null;} the filePath
*/
public String getFilePath() {
return filePath;
}
/**
* Gets the {@link ByteArray} that this instance's data comes from.
*
* @return {@code non-null;} the bytes
*/
public ByteArray getBytes() {
return bytes;
}
/** {@inheritDoc} */
public int getMagic() {
parseToInterfacesIfNecessary();
return getMagic0();
}
/** {@inheritDoc} */
public int getMinorVersion() {
parseToInterfacesIfNecessary();
return getMinorVersion0();
}
/** {@inheritDoc} */
public int getMajorVersion() {
parseToInterfacesIfNecessary();
return getMajorVersion0();
}
/** {@inheritDoc} */
public int getAccessFlags() {
parseToInterfacesIfNecessary();
return accessFlags;
}
/** {@inheritDoc} */
public CstType getThisClass() {
parseToInterfacesIfNecessary();
return thisClass;
}
/** {@inheritDoc} */
public CstType getSuperclass() {
parseToInterfacesIfNecessary();
return superClass;
}
/** {@inheritDoc} */
public ConstantPool getConstantPool() {
parseToInterfacesIfNecessary();
return pool;
}
/** {@inheritDoc} */
public TypeList getInterfaces() {
parseToInterfacesIfNecessary();
return interfaces;
}
/** {@inheritDoc} */
public FieldList getFields() {
parseToEndIfNecessary();
return fields;
}
/** {@inheritDoc} */
public MethodList getMethods() {
parseToEndIfNecessary();
return methods;
}
/** {@inheritDoc} */
public AttributeList getAttributes() {
parseToEndIfNecessary();
return attributes;
}
/** {@inheritDoc} */
public CstString getSourceFile() {
AttributeList attribs = getAttributes();
Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME);
if (attSf instanceof AttSourceFile) {
return ((AttSourceFile) attSf).getSourceFile();
}
return null;
}
/**
* Constructs and returns an instance of {@link TypeList} whose
* data comes from the bytes of this instance, interpreted as a
* list of constant pool indices for classes, which are in turn
* translated to type constants. Instance construction will fail
* if any of the (alleged) indices turn out not to refer to
* constant pool entries of type {@code Class}.
*
* @param offset offset into {@link #bytes} for the start of the
* data
* @param size number of elements in the list (not number of bytes)
* @return {@code non-null;} an appropriately-constructed class list
*/
public TypeList makeTypeList(int offset, int size) {
if (size == 0) {
return StdTypeList.EMPTY;
}
if (pool == null) {
throw new IllegalStateException("pool not yet initialized");
}
return new DcfTypeList(bytes, offset, size, pool, observer);
}
/**
* Gets the class file field {@code magic}, but without doing any
* checks or parsing first.
*
* @return the magic value
*/
public int getMagic0() {
return bytes.getInt(0);
}
/**
* Gets the class file field {@code minor_version}, but
* without doing any checks or parsing first.
*
* @return the minor version
*/
public int getMinorVersion0() {
return bytes.getUnsignedShort(4);
}
/**
* Gets the class file field {@code major_version}, but
* without doing any checks or parsing first.
*
* @return the major version
*/
public int getMajorVersion0() {
return bytes.getUnsignedShort(6);
}
/**
* Runs {@link #parse} if it has not yet been run to cover up to
* the interfaces list.
*/
private void parseToInterfacesIfNecessary() {
if (accessFlags == -1) {
parse();
}
}
/**
* Runs {@link #parse} if it has not yet been run successfully.
*/
private void parseToEndIfNecessary() {
if (attributes == null) {
parse();
}
}
/**
* Does the parsing, handing exceptions.
*/
private void parse() {
try {
parse0();
} catch (ParseException ex) {
ex.addContext("...while parsing " + filePath);
throw ex;
} catch (RuntimeException ex) {
ParseException pe = new ParseException(ex);
pe.addContext("...while parsing " + filePath);
throw pe;
}
}
/**
* Sees if the .class file header magic/version are within
* range.
*
* @param magic the value of a classfile "magic" field
* @param minorVersion the value of a classfile "minor_version" field
* @param majorVersion the value of a classfile "major_version" field
* @return true iff the parameters are valid and within range
*/
private boolean isGoodVersion(int magic, int minorVersion,
int majorVersion) {
/* Valid version ranges are typically of the form
* "A.0 through B.C inclusive" where A <= B and C >= 0,
* which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
*/
if (magic == CLASS_FILE_MAGIC && minorVersion >= 0) {
/* Check against max first to handle the case where
* MIN_MAJOR == MAX_MAJOR.
*/
if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) {
if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) {
return true;
}
} else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION &&
majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) {
return true;
}
}
return false;
}
/**
* Does the actual parsing.
*/
private void parse0() {
if (bytes.size() < 10) {
throw new ParseException("severely truncated class file");
}
if (observer != null) {
observer.parsed(bytes, 0, 0, "begin classfile");
observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0()));
observer.parsed(bytes, 4, 2,
"minor_version: " + Hex.u2(getMinorVersion0()));
observer.parsed(bytes, 6, 2,
"major_version: " + Hex.u2(getMajorVersion0()));
}
if (strictParse) {
/* Make sure that this looks like a valid class file with a
* version that we can handle.
*/
if (!isGoodVersion(getMagic0(), getMinorVersion0(),
getMajorVersion0())) {
throw new ParseException("bad class file magic (" +
Hex.u4(getMagic0()) +
") or version (" +
Hex.u2(getMajorVersion0()) + "." +
Hex.u2(getMinorVersion0()) + ")");
}
}
ConstantPoolParser cpParser = new ConstantPoolParser(bytes);
cpParser.setObserver(observer);
pool = cpParser.getPool();
pool.setImmutable();
int at = cpParser.getEndOffset();
int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags;
int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class;
thisClass = (CstType) pool.get(cpi);
cpi = bytes.getUnsignedShort(at + 4); // u2 super_class;
superClass = (CstType) pool.get0Ok(cpi);
int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count
if (observer != null) {
observer.parsed(bytes, at, 2,
"access_flags: " +
AccessFlags.classString(accessFlags));
observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass);
observer.parsed(bytes, at + 4, 2, "super_class: " +
stringOrNone(superClass));
observer.parsed(bytes, at + 6, 2,
"interfaces_count: " + Hex.u2(count));
if (count != 0) {
observer.parsed(bytes, at + 8, 0, "interfaces:");
}
}
at += 8;
interfaces = makeTypeList(at, count);
at += count * 2;
if (strictParse) {
/*
* Make sure that the file/jar path matches the declared
* package/class name.
*/
String thisClassName = thisClass.getClassType().getClassName();
if (!(filePath.endsWith(".class") &&
filePath.startsWith(thisClassName) &&
(filePath.length() == (thisClassName.length() + 6)))) {
throw new ParseException("class name (" + thisClassName +
") does not match path (" +
filePath + ")");
}
}
/*
* Only set the instance variable accessFlags here, since
* that's what signals a successful parse of the first part of
* the file (through the interfaces list).
*/
this.accessFlags = accessFlags;
FieldListParser flParser =
new FieldListParser(this, thisClass, at, attributeFactory);
flParser.setObserver(observer);
fields = flParser.getList();
at = flParser.getEndOffset();
MethodListParser mlParser =
new MethodListParser(this, thisClass, at, attributeFactory);
mlParser.setObserver(observer);
methods = mlParser.getList();
at = mlParser.getEndOffset();
AttributeListParser alParser =
new AttributeListParser(this, AttributeFactory.CTX_CLASS, at,
attributeFactory);
alParser.setObserver(observer);
attributes = alParser.getList();
attributes.setImmutable();
at = alParser.getEndOffset();
if (at != bytes.size()) {
throw new ParseException("extra bytes at end of class file, " +
"at offset " + Hex.u4(at));
}
if (observer != null) {
observer.parsed(bytes, at, 0, "end classfile");
}
}
/**
* Implementation of {@link TypeList} whose data comes directly
* from the bytes of an instance of this (outer) class,
* interpreted as a list of constant pool indices for classes
* which are in turn returned as type constants. Instance
* construction will fail if any of the (alleged) indices turn out
* not to refer to constant pool entries of type
* {@code Class}.
*/
private static class DcfTypeList implements TypeList {
/** {@code non-null;} array containing the data */
private final ByteArray bytes;
/** number of elements in the list (not number of bytes) */
private final int size;
/** {@code non-null;} the constant pool */
private final StdConstantPool pool;
/**
* Constructs an instance.
*
* @param bytes {@code non-null;} original classfile's bytes
* @param offset offset into {@link #bytes} for the start of the
* data
* @param size number of elements in the list (not number of bytes)
* @param pool {@code non-null;} the constant pool to use
* @param observer {@code null-ok;} parse observer to use, if any
*/
public DcfTypeList(ByteArray bytes, int offset, int size,
StdConstantPool pool, ParseObserver observer) {
if (size < 0) {
throw new IllegalArgumentException("size < 0");
}
bytes = bytes.slice(offset, offset + size * 2);
this.bytes = bytes;
this.size = size;
this.pool = pool;
for (int i = 0; i < size; i++) {
offset = i * 2;
int idx = bytes.getUnsignedShort(offset);
CstType type;
try {
type = (CstType) pool.get(idx);
} catch (ClassCastException ex) {
// Translate the exception.
throw new RuntimeException("bogus class cpi", ex);
}
if (observer != null) {
observer.parsed(bytes, offset, 2, " " + type);
}
}
}
/** {@inheritDoc} */
public boolean isMutable() {
return false;
}
/** {@inheritDoc} */
public int size() {
return size;
}
/** {@inheritDoc} */
public int getWordCount() {
// It is the same as size because all elements are classes.
return size;
}
/** {@inheritDoc} */
public Type getType(int n) {
int idx = bytes.getUnsignedShort(n * 2);
return ((CstType) pool.get(idx)).getClassType();
}
/** {@inheritDoc} */
public TypeList withAddedType(Type type) {
throw new UnsupportedOperationException("unsupported");
}
}
}