/* * 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"); } } }