| /* |
| * 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[]</code> 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</code> 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</code> file major version */ |
| private static final int CLASS_FILE_MAX_MAJOR_VERSION = 50; |
| |
| /** maximum <code>.class</code> file minor version */ |
| private static final int CLASS_FILE_MAX_MINOR_VERSION = 0; |
| |
| /** |
| * non-null; the file path for the class, excluding any base directory |
| * specification |
| */ |
| private final String filePath; |
| |
| /** non-null; the bytes of the file */ |
| private final ByteArray bytes; |
| |
| /** |
| * whether to be strict about parsing; if |
| * <code>false</code>, 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; |
| |
| /** |
| * null-ok; the constant pool; only ever <code>null</code> |
| * before the constant pool is successfully parsed |
| */ |
| private StdConstantPool pool; |
| |
| /** |
| * the class file field <code>access_flags</code>; will be <code>-1</code> |
| * before the file is successfully parsed |
| */ |
| private int accessFlags; |
| |
| /** |
| * null-ok; the class file field <code>this_class</code>, |
| * interpreted as a type constant; only ever <code>null</code> |
| * before the file is successfully parsed |
| */ |
| private CstType thisClass; |
| |
| /** |
| * null-ok; the class file field <code>super_class</code>, interpreted |
| * as a type constant if non-zero |
| */ |
| private CstType superClass; |
| |
| /** |
| * null-ok; the class file field <code>interfaces</code>; only |
| * ever <code>null</code> before the file is successfully |
| * parsed |
| */ |
| private TypeList interfaces; |
| |
| /** |
| * null-ok; the class file field <code>fields</code>; only ever |
| * <code>null</code> before the file is successfully parsed |
| */ |
| private FieldList fields; |
| |
| /** |
| * null-ok; the class file field <code>methods</code>; only ever |
| * <code>null</code> before the file is successfully parsed |
| */ |
| private MethodList methods; |
| |
| /** |
| * null-ok; the class file field <code>attributes</code>; only |
| * ever <code>null</code> before the file is successfully |
| * parsed |
| */ |
| private StdAttributeList attributes; |
| |
| /** null-ok; attribute factory, if any */ |
| private AttributeFactory attributeFactory; |
| |
| /** null-ok; parse observer, if any */ |
| private ParseObserver observer; |
| |
| /** |
| * Returns the string form of an object or <code>"(none)"</code> |
| * (rather than <code>"null"</code>) for <code>null</code>. |
| * |
| * @param obj null-ok; the object to stringify |
| * @return 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 non-null; the bytes of the file |
| * @param filePath non-null; the file path for the class, |
| * excluding any base directory specification |
| * @param strictParse whether to be strict about parsing; if |
| * <code>false</code>, 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 non-null; the bytes of the file |
| * @param filePath non-null; the file path for the class, |
| * excluding any base directory specification |
| * @param strictParse whether to be strict about parsing; if |
| * <code>false</code>, 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 null-ok; the observer |
| */ |
| public void setObserver(ParseObserver observer) { |
| this.observer = observer; |
| } |
| |
| /** |
| * Sets the attribute factory to use. |
| * |
| * @param attributeFactory 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 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</code>. |
| * |
| * @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 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</code>, 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</code>, 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</code>, 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</code>. |
| */ |
| private static class DcfTypeList implements TypeList { |
| /** non-null; array containing the data */ |
| private final ByteArray bytes; |
| |
| /** number of elements in the list (not number of bytes) */ |
| private final int size; |
| |
| /** non-null; the constant pool */ |
| private final StdConstantPool pool; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param bytes 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 non-null; the constant pool to use |
| * @param observer 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"); |
| } |
| } |
| } |