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