DirectClassFile.java revision 9dbd802c8c96c3a66873bc600bc7d1374a1d08e5
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
17package com.android.dx.cf.direct;
18
19import com.android.dx.cf.attrib.AttBootstrapMethods;
20import com.android.dx.cf.attrib.AttSourceFile;
21import com.android.dx.cf.code.BootstrapMethodsList;
22import com.android.dx.cf.cst.ConstantPoolParser;
23import com.android.dx.cf.iface.Attribute;
24import com.android.dx.cf.iface.AttributeList;
25import com.android.dx.cf.iface.ClassFile;
26import com.android.dx.cf.iface.FieldList;
27import com.android.dx.cf.iface.MethodList;
28import com.android.dx.cf.iface.ParseException;
29import com.android.dx.cf.iface.ParseObserver;
30import com.android.dx.cf.iface.StdAttributeList;
31import com.android.dx.rop.code.AccessFlags;
32import com.android.dx.rop.cst.ConstantPool;
33import com.android.dx.rop.cst.CstString;
34import com.android.dx.rop.cst.CstType;
35import com.android.dx.rop.cst.StdConstantPool;
36import com.android.dx.rop.type.StdTypeList;
37import com.android.dx.rop.type.Type;
38import com.android.dx.rop.type.TypeList;
39import com.android.dx.util.ByteArray;
40import com.android.dx.util.Hex;
41
42/**
43 * Class file with info taken from a {@code byte[]} or slice thereof.
44 */
45public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
249    public ByteArray getBytes() {
250        return bytes;
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public int getMagic() {
256        parseToInterfacesIfNecessary();
257        return getMagic0();
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public int getMinorVersion() {
263        parseToInterfacesIfNecessary();
264        return getMinorVersion0();
265    }
266
267    /** {@inheritDoc} */
268    @Override
269    public int getMajorVersion() {
270        parseToInterfacesIfNecessary();
271        return getMajorVersion0();
272    }
273
274    /** {@inheritDoc} */
275    @Override
276    public int getAccessFlags() {
277        parseToInterfacesIfNecessary();
278        return accessFlags;
279    }
280
281    /** {@inheritDoc} */
282    @Override
283    public CstType getThisClass() {
284        parseToInterfacesIfNecessary();
285        return thisClass;
286    }
287
288    /** {@inheritDoc} */
289    @Override
290    public CstType getSuperclass() {
291        parseToInterfacesIfNecessary();
292        return superClass;
293    }
294
295    /** {@inheritDoc} */
296    @Override
297    public ConstantPool getConstantPool() {
298        parseToInterfacesIfNecessary();
299        return pool;
300    }
301
302    /** {@inheritDoc} */
303    @Override
304    public TypeList getInterfaces() {
305        parseToInterfacesIfNecessary();
306        return interfaces;
307    }
308
309    /** {@inheritDoc} */
310    @Override
311    public FieldList getFields() {
312        parseToEndIfNecessary();
313        return fields;
314    }
315
316    /** {@inheritDoc} */
317    @Override
318    public MethodList getMethods() {
319        parseToEndIfNecessary();
320        return methods;
321    }
322
323    /** {@inheritDoc} */
324    @Override
325    public AttributeList getAttributes() {
326        parseToEndIfNecessary();
327        return attributes;
328    }
329
330    /** {@inheritDoc} */
331    @Override
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
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     */
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     */
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     */
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     */
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     */
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     */
423    private void parseToEndIfNecessary() {
424        if (attributes == null) {
425            parse();
426        }
427    }
428
429    /**
430     * Does the parsing, handing exceptions.
431     */
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     */
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     */
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     */
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         */
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
658        public boolean isMutable() {
659            return false;
660        }
661
662        /** {@inheritDoc} */
663        @Override
664        public int size() {
665            return size;
666        }
667
668        /** {@inheritDoc} */
669        @Override
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
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
684        public TypeList withAddedType(Type type) {
685            throw new UnsupportedOperationException("unsupported");
686        }
687    }
688}
689