diff options
Diffstat (limited to 'dx/src/com/android/dx/cf/direct/DirectClassFile.java')
-rw-r--r-- | dx/src/com/android/dx/cf/direct/DirectClassFile.java | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/cf/direct/DirectClassFile.java b/dx/src/com/android/dx/cf/direct/DirectClassFile.java new file mode 100644 index 0000000..b194572 --- /dev/null +++ b/dx/src/com/android/dx/cf/direct/DirectClassFile.java @@ -0,0 +1,633 @@ +/* + * 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.CstType; +import com.android.dx.rop.cst.CstUtf8; +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 + * + * The class file definition (vmspec/2nd-edition) says: + * + * "Implementations of version 1.2 of the + * Java 2 platform can support class file + * formats of versions in the range 45.0 + * through 46.0 inclusive." + * + * The class files generated by the build are currently + * (as of 11/2006) reporting version 49.0 (0x31.0x00), + * however, so we use that as our upper bound. + * + * 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 */ + private static final int CLASS_FILE_MAX_MAJOR_VERSION = 50; + + /** 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 {@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 CstUtf8 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"); + } + } +} |