summaryrefslogtreecommitdiff
path: root/dx/src/com/android/dx/cf/direct/DirectClassFile.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/dx/cf/direct/DirectClassFile.java')
-rw-r--r--dx/src/com/android/dx/cf/direct/DirectClassFile.java633
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");
+ }
+ }
+}