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      */
stringOrNone(Object obj)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      */
DirectClassFile(ByteArray bytes, String filePath, boolean strictParse)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      */
DirectClassFile(byte[] bytes, String filePath, boolean strictParse)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      */
setObserver(ParseObserver observer)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      */
setAttributeFactory(AttributeFactory attributeFactory)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      */
getFilePath()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      */
getBytes()245     public ByteArray getBytes() {
246         return bytes;
247     }
248 
249     /** {@inheritDoc} */
getMagic()250     public int getMagic() {
251         parseToInterfacesIfNecessary();
252         return getMagic0();
253     }
254 
255     /** {@inheritDoc} */
getMinorVersion()256     public int getMinorVersion() {
257         parseToInterfacesIfNecessary();
258         return getMinorVersion0();
259     }
260 
261     /** {@inheritDoc} */
getMajorVersion()262     public int getMajorVersion() {
263         parseToInterfacesIfNecessary();
264         return getMajorVersion0();
265     }
266 
267     /** {@inheritDoc} */
getAccessFlags()268     public int getAccessFlags() {
269         parseToInterfacesIfNecessary();
270         return accessFlags;
271     }
272 
273     /** {@inheritDoc} */
getThisClass()274     public CstType getThisClass() {
275         parseToInterfacesIfNecessary();
276         return thisClass;
277     }
278 
279     /** {@inheritDoc} */
getSuperclass()280     public CstType getSuperclass() {
281         parseToInterfacesIfNecessary();
282         return superClass;
283     }
284 
285     /** {@inheritDoc} */
getConstantPool()286     public ConstantPool getConstantPool() {
287         parseToInterfacesIfNecessary();
288         return pool;
289     }
290 
291     /** {@inheritDoc} */
getInterfaces()292     public TypeList getInterfaces() {
293         parseToInterfacesIfNecessary();
294         return interfaces;
295     }
296 
297     /** {@inheritDoc} */
getFields()298     public FieldList getFields() {
299         parseToEndIfNecessary();
300         return fields;
301     }
302 
303     /** {@inheritDoc} */
getMethods()304     public MethodList getMethods() {
305         parseToEndIfNecessary();
306         return methods;
307     }
308 
309     /** {@inheritDoc} */
getAttributes()310     public AttributeList getAttributes() {
311         parseToEndIfNecessary();
312         return attributes;
313     }
314 
315     /** {@inheritDoc} */
getSourceFile()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      */
makeTypeList(int offset, int size)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      */
getMagic0()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      */
getMinorVersion0()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      */
getMajorVersion0()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      */
parseToInterfacesIfNecessary()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      */
parseToEndIfNecessary()395     private void parseToEndIfNecessary() {
396         if (attributes == null) {
397             parse();
398         }
399     }
400 
401     /**
402      * Does the parsing, handing exceptions.
403      */
parse()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/version are within
419      * range.
420      *
421      * @param magic the value of a classfile "magic" field
422      * @param minorVersion the value of a classfile "minor_version" field
423      * @param majorVersion the value of a classfile "major_version" field
424      * @return true iff the parameters are valid and within range
425      */
isGoodVersion(int magic, int minorVersion, int majorVersion)426     private boolean isGoodVersion(int magic, int minorVersion,
427             int majorVersion) {
428         /* Valid version ranges are typically of the form
429          * "A.0 through B.C inclusive" where A <= B and C >= 0,
430          * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
431          */
432         if (magic == CLASS_FILE_MAGIC && minorVersion >= 0) {
433             /* Check against max first to handle the case where
434              * MIN_MAJOR == MAX_MAJOR.
435              */
436             if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) {
437                 if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) {
438                     return true;
439                 }
440             } else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION &&
441                        majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) {
442                 return true;
443             }
444         }
445 
446         return false;
447     }
448 
449     /**
450      * Does the actual parsing.
451      */
parse0()452     private void parse0() {
453         if (bytes.size() < 10) {
454             throw new ParseException("severely truncated class file");
455         }
456 
457         if (observer != null) {
458             observer.parsed(bytes, 0, 0, "begin classfile");
459             observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0()));
460             observer.parsed(bytes, 4, 2,
461                             "minor_version: " + Hex.u2(getMinorVersion0()));
462             observer.parsed(bytes, 6, 2,
463                             "major_version: " + Hex.u2(getMajorVersion0()));
464         }
465 
466         if (strictParse) {
467             /* Make sure that this looks like a valid class file with a
468              * version that we can handle.
469              */
470             if (!isGoodVersion(getMagic0(), getMinorVersion0(),
471                                getMajorVersion0())) {
472                 throw new ParseException("bad class file magic (" +
473                                          Hex.u4(getMagic0()) +
474                                          ") or version (" +
475                                          Hex.u2(getMajorVersion0()) + "." +
476                                          Hex.u2(getMinorVersion0()) + ")");
477             }
478         }
479 
480         ConstantPoolParser cpParser = new ConstantPoolParser(bytes);
481         cpParser.setObserver(observer);
482         pool = cpParser.getPool();
483         pool.setImmutable();
484 
485         int at = cpParser.getEndOffset();
486         int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags;
487         int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class;
488         thisClass = (CstType) pool.get(cpi);
489         cpi = bytes.getUnsignedShort(at + 4); // u2 super_class;
490         superClass = (CstType) pool.get0Ok(cpi);
491         int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count
492 
493         if (observer != null) {
494             observer.parsed(bytes, at, 2,
495                             "access_flags: " +
496                             AccessFlags.classString(accessFlags));
497             observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass);
498             observer.parsed(bytes, at + 4, 2, "super_class: " +
499                             stringOrNone(superClass));
500             observer.parsed(bytes, at + 6, 2,
501                             "interfaces_count: " + Hex.u2(count));
502             if (count != 0) {
503                 observer.parsed(bytes, at + 8, 0, "interfaces:");
504             }
505         }
506 
507         at += 8;
508         interfaces = makeTypeList(at, count);
509         at += count * 2;
510 
511         if (strictParse) {
512             /*
513              * Make sure that the file/jar path matches the declared
514              * package/class name.
515              */
516             String thisClassName = thisClass.getClassType().getClassName();
517             if (!(filePath.endsWith(".class") &&
518                   filePath.startsWith(thisClassName) &&
519                   (filePath.length() == (thisClassName.length() + 6)))) {
520                 throw new ParseException("class name (" + thisClassName +
521                                          ") does not match path (" +
522                                          filePath + ")");
523             }
524         }
525 
526         /*
527          * Only set the instance variable accessFlags here, since
528          * that's what signals a successful parse of the first part of
529          * the file (through the interfaces list).
530          */
531         this.accessFlags = accessFlags;
532 
533         FieldListParser flParser =
534             new FieldListParser(this, thisClass, at, attributeFactory);
535         flParser.setObserver(observer);
536         fields = flParser.getList();
537         at = flParser.getEndOffset();
538 
539         MethodListParser mlParser =
540             new MethodListParser(this, thisClass, at, attributeFactory);
541         mlParser.setObserver(observer);
542         methods = mlParser.getList();
543         at = mlParser.getEndOffset();
544 
545         AttributeListParser alParser =
546             new AttributeListParser(this, AttributeFactory.CTX_CLASS, at,
547                                     attributeFactory);
548         alParser.setObserver(observer);
549         attributes = alParser.getList();
550         attributes.setImmutable();
551         at = alParser.getEndOffset();
552 
553         if (at != bytes.size()) {
554             throw new ParseException("extra bytes at end of class file, " +
555                                      "at offset " + Hex.u4(at));
556         }
557 
558         if (observer != null) {
559             observer.parsed(bytes, at, 0, "end classfile");
560         }
561     }
562 
563     /**
564      * Implementation of {@link TypeList} whose data comes directly
565      * from the bytes of an instance of this (outer) class,
566      * interpreted as a list of constant pool indices for classes
567      * which are in turn returned as type constants. Instance
568      * construction will fail if any of the (alleged) indices turn out
569      * not to refer to constant pool entries of type
570      * {@code Class}.
571      */
572     private static class DcfTypeList implements TypeList {
573         /** {@code non-null;} array containing the data */
574         private final ByteArray bytes;
575 
576         /** number of elements in the list (not number of bytes) */
577         private final int size;
578 
579         /** {@code non-null;} the constant pool */
580         private final StdConstantPool pool;
581 
582         /**
583          * Constructs an instance.
584          *
585          * @param bytes {@code non-null;} original classfile's bytes
586          * @param offset offset into {@link #bytes} for the start of the
587          * data
588          * @param size number of elements in the list (not number of bytes)
589          * @param pool {@code non-null;} the constant pool to use
590          * @param observer {@code null-ok;} parse observer to use, if any
591          */
DcfTypeList(ByteArray bytes, int offset, int size, StdConstantPool pool, ParseObserver observer)592         public DcfTypeList(ByteArray bytes, int offset, int size,
593                 StdConstantPool pool, ParseObserver observer) {
594             if (size < 0) {
595                 throw new IllegalArgumentException("size < 0");
596             }
597 
598             bytes = bytes.slice(offset, offset + size * 2);
599             this.bytes = bytes;
600             this.size = size;
601             this.pool = pool;
602 
603             for (int i = 0; i < size; i++) {
604                 offset = i * 2;
605                 int idx = bytes.getUnsignedShort(offset);
606                 CstType type;
607                 try {
608                     type = (CstType) pool.get(idx);
609                 } catch (ClassCastException ex) {
610                     // Translate the exception.
611                     throw new RuntimeException("bogus class cpi", ex);
612                 }
613                 if (observer != null) {
614                     observer.parsed(bytes, offset, 2, "  " + type);
615                 }
616             }
617         }
618 
619         /** {@inheritDoc} */
isMutable()620         public boolean isMutable() {
621             return false;
622         }
623 
624         /** {@inheritDoc} */
size()625         public int size() {
626             return size;
627         }
628 
629         /** {@inheritDoc} */
getWordCount()630         public int getWordCount() {
631             // It is the same as size because all elements are classes.
632             return size;
633         }
634 
635         /** {@inheritDoc} */
getType(int n)636         public Type getType(int n) {
637             int idx = bytes.getUnsignedShort(n * 2);
638             return ((CstType) pool.get(idx)).getClassType();
639         }
640 
641         /** {@inheritDoc} */
withAddedType(Type type)642         public TypeList withAddedType(Type type) {
643             throw new UnsupportedOperationException("unsupported");
644         }
645     }
646 }
647