Home | History | Annotate | Download | only in direct
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.cf.direct;
     18 
     19 import com.android.dx.cf.attrib.AttSourceFile;
     20 import com.android.dx.cf.cst.ConstantPoolParser;
     21 import com.android.dx.cf.iface.Attribute;
     22 import com.android.dx.cf.iface.AttributeList;
     23 import com.android.dx.cf.iface.ClassFile;
     24 import com.android.dx.cf.iface.FieldList;
     25 import com.android.dx.cf.iface.MethodList;
     26 import com.android.dx.cf.iface.ParseException;
     27 import com.android.dx.cf.iface.ParseObserver;
     28 import com.android.dx.cf.iface.StdAttributeList;
     29 import com.android.dx.rop.code.AccessFlags;
     30 import com.android.dx.rop.cst.ConstantPool;
     31 import com.android.dx.rop.cst.CstType;
     32 import com.android.dx.rop.cst.CstUtf8;
     33 import com.android.dx.rop.cst.StdConstantPool;
     34 import com.android.dx.rop.type.StdTypeList;
     35 import com.android.dx.rop.type.Type;
     36 import com.android.dx.rop.type.TypeList;
     37 import com.android.dx.util.ByteArray;
     38 import com.android.dx.util.Hex;
     39 
     40 /**
     41  * Class file with info taken from a {@code byte[]} or slice thereof.
     42  */
     43 public class DirectClassFile implements ClassFile {
     44     /** the expected value of the ClassFile.magic field */
     45     private static final int CLASS_FILE_MAGIC = 0xcafebabe;
     46 
     47     /**
     48      * minimum {@code .class} file major version
     49      *
     50      * The class file definition (vmspec/2nd-edition) says:
     51      *
     52      *     "Implementations of version 1.2 of the
     53      *     Java 2 platform can support class file
     54      *     formats of versions in the range 45.0
     55      *     through 46.0 inclusive."
     56      *
     57      * The class files generated by the build are currently
     58      * (as of 11/2006) reporting version 49.0 (0x31.0x00),
     59      * however, so we use that as our upper bound.
     60      *
     61      * Valid ranges are typically of the form
     62      * "A.0 through B.C inclusive" where A <= B and C >= 0,
     63      * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
     64      */
     65     private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45;
     66 
     67     /** maximum {@code .class} file major version */
     68     private static final int CLASS_FILE_MAX_MAJOR_VERSION = 50;
     69 
     70     /** maximum {@code .class} file minor version */
     71     private static final int CLASS_FILE_MAX_MINOR_VERSION = 0;
     72 
     73     /**
     74      * {@code non-null;} the file path for the class, excluding any base directory
     75      * specification
     76      */
     77     private final String filePath;
     78 
     79     /** {@code non-null;} the bytes of the file */
     80     private final ByteArray bytes;
     81 
     82     /**
     83      * whether to be strict about parsing; if
     84      * {@code false}, this avoids doing checks that only exist
     85      * for purposes of verification (such as magic number matching and
     86      * path-package consistency checking)
     87      */
     88     private final boolean strictParse;
     89 
     90     /**
     91      * {@code null-ok;} the constant pool; only ever {@code null}
     92      * before the constant pool is successfully parsed
     93      */
     94     private StdConstantPool pool;
     95 
     96     /**
     97      * the class file field {@code access_flags}; will be {@code -1}
     98      * before the file is successfully parsed
     99      */
    100     private int accessFlags;
    101 
    102     /**
    103      * {@code null-ok;} the class file field {@code this_class},
    104      * interpreted as a type constant; only ever {@code null}
    105      * before the file is successfully parsed
    106      */
    107     private CstType thisClass;
    108 
    109     /**
    110      * {@code null-ok;} the class file field {@code super_class}, interpreted
    111      * as a type constant if non-zero
    112      */
    113     private CstType superClass;
    114 
    115     /**
    116      * {@code null-ok;} the class file field {@code interfaces}; only
    117      * ever {@code null} before the file is successfully
    118      * parsed
    119      */
    120     private TypeList interfaces;
    121 
    122     /**
    123      * {@code null-ok;} the class file field {@code fields}; only ever
    124      * {@code null} before the file is successfully parsed
    125      */
    126     private FieldList fields;
    127 
    128     /**
    129      * {@code null-ok;} the class file field {@code methods}; only ever
    130      * {@code null} before the file is successfully parsed
    131      */
    132     private MethodList methods;
    133 
    134     /**
    135      * {@code null-ok;} the class file field {@code attributes}; only
    136      * ever {@code null} before the file is successfully
    137      * parsed
    138      */
    139     private StdAttributeList attributes;
    140 
    141     /** {@code null-ok;} attribute factory, if any */
    142     private AttributeFactory attributeFactory;
    143 
    144     /** {@code null-ok;} parse observer, if any */
    145     private ParseObserver observer;
    146 
    147     /**
    148      * Returns the string form of an object or {@code "(none)"}
    149      * (rather than {@code "null"}) for {@code null}.
    150      *
    151      * @param obj {@code null-ok;} the object to stringify
    152      * @return {@code non-null;} the appropriate string form
    153      */
    154     public static String stringOrNone(Object obj) {
    155         if (obj == null) {
    156             return "(none)";
    157         }
    158 
    159         return obj.toString();
    160     }
    161 
    162     /**
    163      * Constructs an instance.
    164      *
    165      * @param bytes {@code non-null;} the bytes of the file
    166      * @param filePath {@code non-null;} the file path for the class,
    167      * excluding any base directory specification
    168      * @param strictParse whether to be strict about parsing; if
    169      * {@code false}, this avoids doing checks that only exist
    170      * for purposes of verification (such as magic number matching and
    171      * path-package consistency checking)
    172      */
    173     public DirectClassFile(ByteArray bytes, String filePath,
    174                            boolean strictParse) {
    175         if (bytes == null) {
    176             throw new NullPointerException("bytes == null");
    177         }
    178 
    179         if (filePath == null) {
    180             throw new NullPointerException("filePath == null");
    181         }
    182 
    183         this.filePath = filePath;
    184         this.bytes = bytes;
    185         this.strictParse = strictParse;
    186         this.accessFlags = -1;
    187     }
    188 
    189     /**
    190      * Constructs an instance.
    191      *
    192      * @param bytes {@code non-null;} the bytes of the file
    193      * @param filePath {@code non-null;} the file path for the class,
    194      * excluding any base directory specification
    195      * @param strictParse whether to be strict about parsing; if
    196      * {@code false}, this avoids doing checks that only exist
    197      * for purposes of verification (such as magic number matching and
    198      * path-package consistency checking)
    199      */
    200     public DirectClassFile(byte[] bytes, String filePath,
    201                            boolean strictParse) {
    202         this(new ByteArray(bytes), filePath, strictParse);
    203     }
    204 
    205     /**
    206      * Sets the parse observer for this instance.
    207      *
    208      * @param observer {@code null-ok;} the observer
    209      */
    210     public void setObserver(ParseObserver observer) {
    211         this.observer = observer;
    212     }
    213 
    214     /**
    215      * Sets the attribute factory to use.
    216      *
    217      * @param attributeFactory {@code non-null;} the attribute factory
    218      */
    219     public void setAttributeFactory(AttributeFactory attributeFactory) {
    220         if (attributeFactory == null) {
    221             throw new NullPointerException("attributeFactory == null");
    222         }
    223 
    224         this.attributeFactory = attributeFactory;
    225     }
    226 
    227     /**
    228      * Gets the {@link ByteArray} that this instance's data comes from.
    229      *
    230      * @return {@code non-null;} the bytes
    231      */
    232     public ByteArray getBytes() {
    233         return bytes;
    234     }
    235 
    236     /** {@inheritDoc} */
    237     public int getMagic() {
    238         parseToInterfacesIfNecessary();
    239         return getMagic0();
    240     }
    241 
    242     /** {@inheritDoc} */
    243     public int getMinorVersion() {
    244         parseToInterfacesIfNecessary();
    245         return getMinorVersion0();
    246     }
    247 
    248     /** {@inheritDoc} */
    249     public int getMajorVersion() {
    250         parseToInterfacesIfNecessary();
    251         return getMajorVersion0();
    252     }
    253 
    254     /** {@inheritDoc} */
    255     public int getAccessFlags() {
    256         parseToInterfacesIfNecessary();
    257         return accessFlags;
    258     }
    259 
    260     /** {@inheritDoc} */
    261     public CstType getThisClass() {
    262         parseToInterfacesIfNecessary();
    263         return thisClass;
    264     }
    265 
    266     /** {@inheritDoc} */
    267     public CstType getSuperclass() {
    268         parseToInterfacesIfNecessary();
    269         return superClass;
    270     }
    271 
    272     /** {@inheritDoc} */
    273     public ConstantPool getConstantPool() {
    274         parseToInterfacesIfNecessary();
    275         return pool;
    276     }
    277 
    278     /** {@inheritDoc} */
    279     public TypeList getInterfaces() {
    280         parseToInterfacesIfNecessary();
    281         return interfaces;
    282     }
    283 
    284     /** {@inheritDoc} */
    285     public FieldList getFields() {
    286         parseToEndIfNecessary();
    287         return fields;
    288     }
    289 
    290     /** {@inheritDoc} */
    291     public MethodList getMethods() {
    292         parseToEndIfNecessary();
    293         return methods;
    294     }
    295 
    296     /** {@inheritDoc} */
    297     public AttributeList getAttributes() {
    298         parseToEndIfNecessary();
    299         return attributes;
    300     }
    301 
    302     /** {@inheritDoc} */
    303     public CstUtf8 getSourceFile() {
    304         AttributeList attribs = getAttributes();
    305         Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME);
    306 
    307         if (attSf instanceof AttSourceFile) {
    308             return ((AttSourceFile) attSf).getSourceFile();
    309         }
    310 
    311         return null;
    312     }
    313 
    314     /**
    315      * Constructs and returns an instance of {@link TypeList} whose
    316      * data comes from the bytes of this instance, interpreted as a
    317      * list of constant pool indices for classes, which are in turn
    318      * translated to type constants. Instance construction will fail
    319      * if any of the (alleged) indices turn out not to refer to
    320      * constant pool entries of type {@code Class}.
    321      *
    322      * @param offset offset into {@link #bytes} for the start of the
    323      * data
    324      * @param size number of elements in the list (not number of bytes)
    325      * @return {@code non-null;} an appropriately-constructed class list
    326      */
    327     public TypeList makeTypeList(int offset, int size) {
    328         if (size == 0) {
    329             return StdTypeList.EMPTY;
    330         }
    331 
    332         if (pool == null) {
    333             throw new IllegalStateException("pool not yet initialized");
    334         }
    335 
    336         return new DcfTypeList(bytes, offset, size, pool, observer);
    337     }
    338 
    339     /**
    340      * Gets the class file field {@code magic}, but without doing any
    341      * checks or parsing first.
    342      *
    343      * @return the magic value
    344      */
    345     public int getMagic0() {
    346         return bytes.getInt(0);
    347     }
    348 
    349     /**
    350      * Gets the class file field {@code minor_version}, but
    351      * without doing any checks or parsing first.
    352      *
    353      * @return the minor version
    354      */
    355     public int getMinorVersion0() {
    356         return bytes.getUnsignedShort(4);
    357     }
    358 
    359     /**
    360      * Gets the class file field {@code major_version}, but
    361      * without doing any checks or parsing first.
    362      *
    363      * @return the major version
    364      */
    365     public int getMajorVersion0() {
    366         return bytes.getUnsignedShort(6);
    367     }
    368 
    369     /**
    370      * Runs {@link #parse} if it has not yet been run to cover up to
    371      * the interfaces list.
    372      */
    373     private void parseToInterfacesIfNecessary() {
    374         if (accessFlags == -1) {
    375             parse();
    376         }
    377     }
    378 
    379     /**
    380      * Runs {@link #parse} if it has not yet been run successfully.
    381      */
    382     private void parseToEndIfNecessary() {
    383         if (attributes == null) {
    384             parse();
    385         }
    386     }
    387 
    388     /**
    389      * Does the parsing, handing exceptions.
    390      */
    391     private void parse() {
    392         try {
    393             parse0();
    394         } catch (ParseException ex) {
    395             ex.addContext("...while parsing " + filePath);
    396             throw ex;
    397         } catch (RuntimeException ex) {
    398             ParseException pe = new ParseException(ex);
    399             pe.addContext("...while parsing " + filePath);
    400             throw pe;
    401         }
    402     }
    403 
    404     /**
    405      * Sees if the .class file header magic/version are within
    406      * range.
    407      *
    408      * @param magic the value of a classfile "magic" field
    409      * @param minorVersion the value of a classfile "minor_version" field
    410      * @param majorVersion the value of a classfile "major_version" field
    411      * @return true iff the parameters are valid and within range
    412      */
    413     private boolean isGoodVersion(int magic, int minorVersion,
    414             int majorVersion) {
    415         /* Valid version ranges are typically of the form
    416          * "A.0 through B.C inclusive" where A <= B and C >= 0,
    417          * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
    418          */
    419         if (magic == CLASS_FILE_MAGIC && minorVersion >= 0) {
    420             /* Check against max first to handle the case where
    421              * MIN_MAJOR == MAX_MAJOR.
    422              */
    423             if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) {
    424                 if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) {
    425                     return true;
    426                 }
    427             } else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION &&
    428                        majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) {
    429                 return true;
    430             }
    431         }
    432 
    433         return false;
    434     }
    435 
    436     /**
    437      * Does the actual parsing.
    438      */
    439     private void parse0() {
    440         if (bytes.size() < 10) {
    441             throw new ParseException("severely truncated class file");
    442         }
    443 
    444         if (observer != null) {
    445             observer.parsed(bytes, 0, 0, "begin classfile");
    446             observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0()));
    447             observer.parsed(bytes, 4, 2,
    448                             "minor_version: " + Hex.u2(getMinorVersion0()));
    449             observer.parsed(bytes, 6, 2,
    450                             "major_version: " + Hex.u2(getMajorVersion0()));
    451         }
    452 
    453         if (strictParse) {
    454             /* Make sure that this looks like a valid class file with a
    455              * version that we can handle.
    456              */
    457             if (!isGoodVersion(getMagic0(), getMinorVersion0(),
    458                                getMajorVersion0())) {
    459                 throw new ParseException("bad class file magic (" +
    460                                          Hex.u4(getMagic0()) +
    461                                          ") or version (" +
    462                                          Hex.u2(getMajorVersion0()) + "." +
    463                                          Hex.u2(getMinorVersion0()) + ")");
    464             }
    465         }
    466 
    467         ConstantPoolParser cpParser = new ConstantPoolParser(bytes);
    468         cpParser.setObserver(observer);
    469         pool = cpParser.getPool();
    470         pool.setImmutable();
    471 
    472         int at = cpParser.getEndOffset();
    473         int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags;
    474         int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class;
    475         thisClass = (CstType) pool.get(cpi);
    476         cpi = bytes.getUnsignedShort(at + 4); // u2 super_class;
    477         superClass = (CstType) pool.get0Ok(cpi);
    478         int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count
    479 
    480         if (observer != null) {
    481             observer.parsed(bytes, at, 2,
    482                             "access_flags: " +
    483                             AccessFlags.classString(accessFlags));
    484             observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass);
    485             observer.parsed(bytes, at + 4, 2, "super_class: " +
    486                             stringOrNone(superClass));
    487             observer.parsed(bytes, at + 6, 2,
    488                             "interfaces_count: " + Hex.u2(count));
    489             if (count != 0) {
    490                 observer.parsed(bytes, at + 8, 0, "interfaces:");
    491             }
    492         }
    493 
    494         at += 8;
    495         interfaces = makeTypeList(at, count);
    496         at += count * 2;
    497 
    498         if (strictParse) {
    499             /*
    500              * Make sure that the file/jar path matches the declared
    501              * package/class name.
    502              */
    503             String thisClassName = thisClass.getClassType().getClassName();
    504             if (!(filePath.endsWith(".class") &&
    505                   filePath.startsWith(thisClassName) &&
    506                   (filePath.length() == (thisClassName.length() + 6)))) {
    507                 throw new ParseException("class name (" + thisClassName +
    508                                          ") does not match path (" +
    509                                          filePath + ")");
    510             }
    511         }
    512 
    513         /*
    514          * Only set the instance variable accessFlags here, since
    515          * that's what signals a successful parse of the first part of
    516          * the file (through the interfaces list).
    517          */
    518         this.accessFlags = accessFlags;
    519 
    520         FieldListParser flParser =
    521             new FieldListParser(this, thisClass, at, attributeFactory);
    522         flParser.setObserver(observer);
    523         fields = flParser.getList();
    524         at = flParser.getEndOffset();
    525 
    526         MethodListParser mlParser =
    527             new MethodListParser(this, thisClass, at, attributeFactory);
    528         mlParser.setObserver(observer);
    529         methods = mlParser.getList();
    530         at = mlParser.getEndOffset();
    531 
    532         AttributeListParser alParser =
    533             new AttributeListParser(this, AttributeFactory.CTX_CLASS, at,
    534                                     attributeFactory);
    535         alParser.setObserver(observer);
    536         attributes = alParser.getList();
    537         attributes.setImmutable();
    538         at = alParser.getEndOffset();
    539 
    540         if (at != bytes.size()) {
    541             throw new ParseException("extra bytes at end of class file, " +
    542                                      "at offset " + Hex.u4(at));
    543         }
    544 
    545         if (observer != null) {
    546             observer.parsed(bytes, at, 0, "end classfile");
    547         }
    548     }
    549 
    550     /**
    551      * Implementation of {@link TypeList} whose data comes directly
    552      * from the bytes of an instance of this (outer) class,
    553      * interpreted as a list of constant pool indices for classes
    554      * which are in turn returned as type constants. Instance
    555      * construction will fail if any of the (alleged) indices turn out
    556      * not to refer to constant pool entries of type
    557      * {@code Class}.
    558      */
    559     private static class DcfTypeList implements TypeList {
    560         /** {@code non-null;} array containing the data */
    561         private final ByteArray bytes;
    562 
    563         /** number of elements in the list (not number of bytes) */
    564         private final int size;
    565 
    566         /** {@code non-null;} the constant pool */
    567         private final StdConstantPool pool;
    568 
    569         /**
    570          * Constructs an instance.
    571          *
    572          * @param bytes {@code non-null;} original classfile's bytes
    573          * @param offset offset into {@link #bytes} for the start of the
    574          * data
    575          * @param size number of elements in the list (not number of bytes)
    576          * @param pool {@code non-null;} the constant pool to use
    577          * @param observer {@code null-ok;} parse observer to use, if any
    578          */
    579         public DcfTypeList(ByteArray bytes, int offset, int size,
    580                 StdConstantPool pool, ParseObserver observer) {
    581             if (size < 0) {
    582                 throw new IllegalArgumentException("size < 0");
    583             }
    584 
    585             bytes = bytes.slice(offset, offset + size * 2);
    586             this.bytes = bytes;
    587             this.size = size;
    588             this.pool = pool;
    589 
    590             for (int i = 0; i < size; i++) {
    591                 offset = i * 2;
    592                 int idx = bytes.getUnsignedShort(offset);
    593                 CstType type;
    594                 try {
    595                     type = (CstType) pool.get(idx);
    596                 } catch (ClassCastException ex) {
    597                     // Translate the exception.
    598                     throw new RuntimeException("bogus class cpi", ex);
    599                 }
    600                 if (observer != null) {
    601                     observer.parsed(bytes, offset, 2, "  " + type);
    602                 }
    603             }
    604         }
    605 
    606         /** {@inheritDoc} */
    607         public boolean isMutable() {
    608             return false;
    609         }
    610 
    611         /** {@inheritDoc} */
    612         public int size() {
    613             return size;
    614         }
    615 
    616         /** {@inheritDoc} */
    617         public int getWordCount() {
    618             // It is the same as size because all elements are classes.
    619             return size;
    620         }
    621 
    622         /** {@inheritDoc} */
    623         public Type getType(int n) {
    624             int idx = bytes.getUnsignedShort(n * 2);
    625             return ((CstType) pool.get(idx)).getClassType();
    626         }
    627 
    628         /** {@inheritDoc} */
    629         public TypeList withAddedType(Type type) {
    630             throw new UnsupportedOperationException("unsupported");
    631         }
    632     }
    633 }
    634