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