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.CstString;
32import com.android.dx.rop.cst.CstType;
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     * See http://en.wikipedia.org/wiki/Java_class_file for an up-to-date
51     * list of version numbers. Currently known (taken from that table) are:
52     *
53     *     J2SE 7.0 = 51 (0x33 hex),
54     *     J2SE 6.0 = 50 (0x32 hex),
55     *     J2SE 5.0 = 49 (0x31 hex),
56     *     JDK 1.4 = 48 (0x30 hex),
57     *     JDK 1.3 = 47 (0x2F hex),
58     *     JDK 1.2 = 46 (0x2E hex),
59     *     JDK 1.1 = 45 (0x2D hex).
60     *
61     * Valid ranges are typically of the form
62     * "A.0 through B.C inclusive" where A <= B and C >= 0,
63     * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
64     */
65    private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45;
66
67    /**
68     * maximum {@code .class} file major version
69     *
70     * Note: if you change this, please change "java.class.version" in System.java.
71     */
72    private static final int CLASS_FILE_MAX_MAJOR_VERSION = 51;
73
74    /** maximum {@code .class} file minor version */
75    private static final int CLASS_FILE_MAX_MINOR_VERSION = 0;
76
77    /**
78     * {@code non-null;} the file path for the class, excluding any base directory
79     * specification
80     */
81    private final String filePath;
82
83    /** {@code non-null;} the bytes of the file */
84    private final ByteArray bytes;
85
86    /**
87     * whether to be strict about parsing; if
88     * {@code false}, this avoids doing checks that only exist
89     * for purposes of verification (such as magic number matching and
90     * path-package consistency checking)
91     */
92    private final boolean strictParse;
93
94    /**
95     * {@code null-ok;} the constant pool; only ever {@code null}
96     * before the constant pool is successfully parsed
97     */
98    private StdConstantPool pool;
99
100    /**
101     * the class file field {@code access_flags}; will be {@code -1}
102     * before the file is successfully parsed
103     */
104    private int accessFlags;
105
106    /**
107     * {@code null-ok;} the class file field {@code this_class},
108     * interpreted as a type constant; only ever {@code null}
109     * before the file is successfully parsed
110     */
111    private CstType thisClass;
112
113    /**
114     * {@code null-ok;} the class file field {@code super_class}, interpreted
115     * as a type constant if non-zero
116     */
117    private CstType superClass;
118
119    /**
120     * {@code null-ok;} the class file field {@code interfaces}; only
121     * ever {@code null} before the file is successfully
122     * parsed
123     */
124    private TypeList interfaces;
125
126    /**
127     * {@code null-ok;} the class file field {@code fields}; only ever
128     * {@code null} before the file is successfully parsed
129     */
130    private FieldList fields;
131
132    /**
133     * {@code null-ok;} the class file field {@code methods}; only ever
134     * {@code null} before the file is successfully parsed
135     */
136    private MethodList methods;
137
138    /**
139     * {@code null-ok;} the class file field {@code attributes}; only
140     * ever {@code null} before the file is successfully
141     * parsed
142     */
143    private StdAttributeList attributes;
144
145    /** {@code null-ok;} attribute factory, if any */
146    private AttributeFactory attributeFactory;
147
148    /** {@code null-ok;} parse observer, if any */
149    private ParseObserver observer;
150
151    /**
152     * Returns the string form of an object or {@code "(none)"}
153     * (rather than {@code "null"}) for {@code null}.
154     *
155     * @param obj {@code null-ok;} the object to stringify
156     * @return {@code non-null;} the appropriate string form
157     */
158    public static String stringOrNone(Object obj) {
159        if (obj == null) {
160            return "(none)";
161        }
162
163        return obj.toString();
164    }
165
166    /**
167     * Constructs an instance.
168     *
169     * @param bytes {@code non-null;} the bytes of the file
170     * @param filePath {@code non-null;} the file path for the class,
171     * excluding any base directory specification
172     * @param strictParse whether to be strict about parsing; if
173     * {@code false}, this avoids doing checks that only exist
174     * for purposes of verification (such as magic number matching and
175     * path-package consistency checking)
176     */
177    public DirectClassFile(ByteArray bytes, String filePath,
178                           boolean strictParse) {
179        if (bytes == null) {
180            throw new NullPointerException("bytes == null");
181        }
182
183        if (filePath == null) {
184            throw new NullPointerException("filePath == null");
185        }
186
187        this.filePath = filePath;
188        this.bytes = bytes;
189        this.strictParse = strictParse;
190        this.accessFlags = -1;
191    }
192
193    /**
194     * Constructs an instance.
195     *
196     * @param bytes {@code non-null;} the bytes of the file
197     * @param filePath {@code non-null;} the file path for the class,
198     * excluding any base directory specification
199     * @param strictParse whether to be strict about parsing; if
200     * {@code false}, this avoids doing checks that only exist
201     * for purposes of verification (such as magic number matching and
202     * path-package consistency checking)
203     */
204    public DirectClassFile(byte[] bytes, String filePath,
205                           boolean strictParse) {
206        this(new ByteArray(bytes), filePath, strictParse);
207    }
208
209    /**
210     * Sets the parse observer for this instance.
211     *
212     * @param observer {@code null-ok;} the observer
213     */
214    public void setObserver(ParseObserver observer) {
215        this.observer = observer;
216    }
217
218    /**
219     * Sets the attribute factory to use.
220     *
221     * @param attributeFactory {@code non-null;} the attribute factory
222     */
223    public void setAttributeFactory(AttributeFactory attributeFactory) {
224        if (attributeFactory == null) {
225            throw new NullPointerException("attributeFactory == null");
226        }
227
228        this.attributeFactory = attributeFactory;
229    }
230
231    /**
232     * Gets the path where this class file is located.
233     *
234     * @return {@code non-null;} the filePath
235     */
236    public String getFilePath() {
237      return filePath;
238    }
239
240    /**
241     * Gets the {@link ByteArray} that this instance's data comes from.
242     *
243     * @return {@code non-null;} the bytes
244     */
245    public ByteArray getBytes() {
246        return bytes;
247    }
248
249    /** {@inheritDoc} */
250    public int getMagic() {
251        parseToInterfacesIfNecessary();
252        return getMagic0();
253    }
254
255    /** {@inheritDoc} */
256    public int getMinorVersion() {
257        parseToInterfacesIfNecessary();
258        return getMinorVersion0();
259    }
260
261    /** {@inheritDoc} */
262    public int getMajorVersion() {
263        parseToInterfacesIfNecessary();
264        return getMajorVersion0();
265    }
266
267    /** {@inheritDoc} */
268    public int getAccessFlags() {
269        parseToInterfacesIfNecessary();
270        return accessFlags;
271    }
272
273    /** {@inheritDoc} */
274    public CstType getThisClass() {
275        parseToInterfacesIfNecessary();
276        return thisClass;
277    }
278
279    /** {@inheritDoc} */
280    public CstType getSuperclass() {
281        parseToInterfacesIfNecessary();
282        return superClass;
283    }
284
285    /** {@inheritDoc} */
286    public ConstantPool getConstantPool() {
287        parseToInterfacesIfNecessary();
288        return pool;
289    }
290
291    /** {@inheritDoc} */
292    public TypeList getInterfaces() {
293        parseToInterfacesIfNecessary();
294        return interfaces;
295    }
296
297    /** {@inheritDoc} */
298    public FieldList getFields() {
299        parseToEndIfNecessary();
300        return fields;
301    }
302
303    /** {@inheritDoc} */
304    public MethodList getMethods() {
305        parseToEndIfNecessary();
306        return methods;
307    }
308
309    /** {@inheritDoc} */
310    public AttributeList getAttributes() {
311        parseToEndIfNecessary();
312        return attributes;
313    }
314
315    /** {@inheritDoc} */
316    public CstString getSourceFile() {
317        AttributeList attribs = getAttributes();
318        Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME);
319
320        if (attSf instanceof AttSourceFile) {
321            return ((AttSourceFile) attSf).getSourceFile();
322        }
323
324        return null;
325    }
326
327    /**
328     * Constructs and returns an instance of {@link TypeList} whose
329     * data comes from the bytes of this instance, interpreted as a
330     * list of constant pool indices for classes, which are in turn
331     * translated to type constants. Instance construction will fail
332     * if any of the (alleged) indices turn out not to refer to
333     * constant pool entries of type {@code Class}.
334     *
335     * @param offset offset into {@link #bytes} for the start of the
336     * data
337     * @param size number of elements in the list (not number of bytes)
338     * @return {@code non-null;} an appropriately-constructed class list
339     */
340    public TypeList makeTypeList(int offset, int size) {
341        if (size == 0) {
342            return StdTypeList.EMPTY;
343        }
344
345        if (pool == null) {
346            throw new IllegalStateException("pool not yet initialized");
347        }
348
349        return new DcfTypeList(bytes, offset, size, pool, observer);
350    }
351
352    /**
353     * Gets the class file field {@code magic}, but without doing any
354     * checks or parsing first.
355     *
356     * @return the magic value
357     */
358    public int getMagic0() {
359        return bytes.getInt(0);
360    }
361
362    /**
363     * Gets the class file field {@code minor_version}, but
364     * without doing any checks or parsing first.
365     *
366     * @return the minor version
367     */
368    public int getMinorVersion0() {
369        return bytes.getUnsignedShort(4);
370    }
371
372    /**
373     * Gets the class file field {@code major_version}, but
374     * without doing any checks or parsing first.
375     *
376     * @return the major version
377     */
378    public int getMajorVersion0() {
379        return bytes.getUnsignedShort(6);
380    }
381
382    /**
383     * Runs {@link #parse} if it has not yet been run to cover up to
384     * the interfaces list.
385     */
386    private void parseToInterfacesIfNecessary() {
387        if (accessFlags == -1) {
388            parse();
389        }
390    }
391
392    /**
393     * Runs {@link #parse} if it has not yet been run successfully.
394     */
395    private void parseToEndIfNecessary() {
396        if (attributes == null) {
397            parse();
398        }
399    }
400
401    /**
402     * Does the parsing, handing exceptions.
403     */
404    private void parse() {
405        try {
406            parse0();
407        } catch (ParseException ex) {
408            ex.addContext("...while parsing " + filePath);
409            throw ex;
410        } catch (RuntimeException ex) {
411            ParseException pe = new ParseException(ex);
412            pe.addContext("...while parsing " + filePath);
413            throw pe;
414        }
415    }
416
417    /**
418     * Sees if the .class file header magic/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     */
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     */
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         */
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} */
620        public boolean isMutable() {
621            return false;
622        }
623
624        /** {@inheritDoc} */
625        public int size() {
626            return size;
627        }
628
629        /** {@inheritDoc} */
630        public int getWordCount() {
631            // It is the same as size because all elements are classes.
632            return size;
633        }
634
635        /** {@inheritDoc} */
636        public Type getType(int n) {
637            int idx = bytes.getUnsignedShort(n * 2);
638            return ((CstType) pool.get(idx)).getClassType();
639        }
640
641        /** {@inheritDoc} */
642        public TypeList withAddedType(Type type) {
643            throw new UnsupportedOperationException("unsupported");
644        }
645    }
646}
647