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