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.dex.file;
18
19import com.android.dx.dex.DexFormat;
20import com.android.dx.dex.DexOptions;
21import com.android.dx.rop.cst.Constant;
22import com.android.dx.rop.cst.CstBaseMethodRef;
23import com.android.dx.rop.cst.CstEnumRef;
24import com.android.dx.rop.cst.CstFieldRef;
25import com.android.dx.rop.cst.CstString;
26import com.android.dx.rop.cst.CstType;
27import com.android.dx.rop.type.Type;
28import com.android.dx.util.ByteArrayAnnotatedOutput;
29import com.android.dx.util.ExceptionWithContext;
30
31import java.io.IOException;
32import java.io.OutputStream;
33import java.io.Writer;
34import java.security.DigestException;
35import java.security.MessageDigest;
36import java.security.NoSuchAlgorithmException;
37import java.util.zip.Adler32;
38
39import static com.android.dx.dex.file.MixedItemSection.SortType;
40
41/**
42 * Representation of an entire {@code .dex} (Dalvik EXecutable)
43 * file, which itself consists of a set of Dalvik classes.
44 */
45public final class DexFile {
46    /** options controlling the creation of the file */
47    private DexOptions dexOptions;
48
49    /** {@code non-null;} word data section */
50    private final MixedItemSection wordData;
51
52    /**
53     * {@code non-null;} type lists section. This is word data, but separating
54     * it from {@link #wordData} helps break what would otherwise be a
55     * circular dependency between the that and {@link #protoIds}.
56     */
57    private final MixedItemSection typeLists;
58
59    /**
60     * {@code non-null;} map section. The map needs to be in a section by itself
61     * for the self-reference mechanics to work in a reasonably
62     * straightforward way. See {@link MapItem#addMap} for more detail.
63     */
64    private final MixedItemSection map;
65
66    /** {@code non-null;} string data section */
67    private final MixedItemSection stringData;
68
69    /** {@code non-null;} string identifiers section */
70    private final StringIdsSection stringIds;
71
72    /** {@code non-null;} type identifiers section */
73    private final TypeIdsSection typeIds;
74
75    /** {@code non-null;} prototype identifiers section */
76    private final ProtoIdsSection protoIds;
77
78    /** {@code non-null;} field identifiers section */
79    private final FieldIdsSection fieldIds;
80
81    /** {@code non-null;} method identifiers section */
82    private final MethodIdsSection methodIds;
83
84    /** {@code non-null;} class definitions section */
85    private final ClassDefsSection classDefs;
86
87    /** {@code non-null;} class data section */
88    private final MixedItemSection classData;
89
90    /** {@code non-null;} byte data section */
91    private final MixedItemSection byteData;
92
93    /** {@code non-null;} file header */
94    private final HeaderSection header;
95
96    /**
97     * {@code non-null;} array of sections in the order they will appear in the
98     * final output file
99     */
100    private final Section[] sections;
101
102    /** {@code >= -1;} total file size or {@code -1} if unknown */
103    private int fileSize;
104
105    /** {@code >= 40;} maximum width of the file dump */
106    private int dumpWidth;
107
108    /**
109     * Constructs an instance. It is initially empty.
110     */
111    public DexFile(DexOptions dexOptions) {
112        this.dexOptions = dexOptions;
113
114        header = new HeaderSection(this);
115        typeLists = new MixedItemSection(null, this, 4, SortType.NONE);
116        wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE);
117        stringData =
118            new MixedItemSection("string_data", this, 1, SortType.INSTANCE);
119        classData = new MixedItemSection(null, this, 1, SortType.NONE);
120        byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE);
121        stringIds = new StringIdsSection(this);
122        typeIds = new TypeIdsSection(this);
123        protoIds = new ProtoIdsSection(this);
124        fieldIds = new FieldIdsSection(this);
125        methodIds = new MethodIdsSection(this);
126        classDefs = new ClassDefsSection(this);
127        map = new MixedItemSection("map", this, 4, SortType.NONE);
128
129        /*
130         * This is the list of sections in the order they appear in
131         * the final output.
132         */
133        sections = new Section[] {
134            header, stringIds, typeIds, protoIds, fieldIds, methodIds,
135            classDefs, wordData, typeLists, stringData, byteData,
136            classData, map };
137
138        fileSize = -1;
139        dumpWidth = 79;
140    }
141
142    /**
143     * Returns true if this dex doesn't contain any class defs.
144     */
145    public boolean isEmpty() {
146        return classDefs.items().isEmpty();
147    }
148
149    /**
150     * Gets the dex-creation options object.
151     */
152    public DexOptions getDexOptions() {
153        return dexOptions;
154    }
155
156    /**
157     * Adds a class to this instance. It is illegal to attempt to add more
158     * than one class with the same name.
159     *
160     * @param clazz {@code non-null;} the class to add
161     */
162    public void add(ClassDefItem clazz) {
163        classDefs.add(clazz);
164    }
165
166    /**
167     * Gets the class definition with the given name, if any.
168     *
169     * @param name {@code non-null;} the class name to look for
170     * @return {@code null-ok;} the class with the given name, or {@code null}
171     * if there is no such class
172     */
173    public ClassDefItem getClassOrNull(String name) {
174        try {
175            Type type = Type.internClassName(name);
176            return (ClassDefItem) classDefs.get(new CstType(type));
177        } catch (IllegalArgumentException ex) {
178            // Translate exception, per contract.
179            return null;
180        }
181    }
182
183    /**
184     * Writes the contents of this instance as either a binary or a
185     * human-readable form, or both.
186     *
187     * @param out {@code null-ok;} where to write to
188     * @param humanOut {@code null-ok;} where to write human-oriented output to
189     * @param verbose whether to be verbose when writing human-oriented output
190     */
191    public void writeTo(OutputStream out, Writer humanOut, boolean verbose)
192        throws IOException {
193        boolean annotate = (humanOut != null);
194        ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
195
196        if (out != null) {
197            out.write(result.getArray());
198        }
199
200        if (annotate) {
201            result.writeAnnotationsTo(humanOut);
202        }
203    }
204
205    /**
206     * Returns the contents of this instance as a {@code .dex} file,
207     * in {@code byte[]} form.
208     *
209     * @param humanOut {@code null-ok;} where to write human-oriented output to
210     * @param verbose whether to be verbose when writing human-oriented output
211     * @return {@code non-null;} a {@code .dex} file for this instance
212     */
213    public byte[] toDex(Writer humanOut, boolean verbose)
214        throws IOException {
215        boolean annotate = (humanOut != null);
216        ByteArrayAnnotatedOutput result = toDex0(annotate, verbose);
217
218        if (annotate) {
219            result.writeAnnotationsTo(humanOut);
220        }
221
222        return result.getArray();
223    }
224
225    /**
226     * Sets the maximum width of the human-oriented dump of the instance.
227     *
228     * @param dumpWidth {@code >= 40;} the width
229     */
230    public void setDumpWidth(int dumpWidth) {
231        if (dumpWidth < 40) {
232            throw new IllegalArgumentException("dumpWidth < 40");
233        }
234
235        this.dumpWidth = dumpWidth;
236    }
237
238    /**
239     * Gets the total file size, if known.
240     *
241     * <p>This is package-scope in order to allow
242     * the {@link HeaderSection} to set itself up properly.</p>
243     *
244     * @return {@code >= 0;} the total file size
245     * @throws RuntimeException thrown if the file size is not yet known
246     */
247    /*package*/ int getFileSize() {
248        if (fileSize < 0) {
249            throw new RuntimeException("file size not yet known");
250        }
251
252        return fileSize;
253    }
254
255    /**
256     * Gets the string data section.
257     *
258     * <p>This is package-scope in order to allow
259     * the various {@link Item} instances to add items to the
260     * instance.</p>
261     *
262     * @return {@code non-null;} the string data section
263     */
264    /*package*/ MixedItemSection getStringData() {
265        return stringData;
266    }
267
268    /**
269     * Gets the word data section.
270     *
271     * <p>This is package-scope in order to allow
272     * the various {@link Item} instances to add items to the
273     * instance.</p>
274     *
275     * @return {@code non-null;} the word data section
276     */
277    /*package*/ MixedItemSection getWordData() {
278        return wordData;
279    }
280
281    /**
282     * Gets the type lists section.
283     *
284     * <p>This is package-scope in order to allow
285     * the various {@link Item} instances to add items to the
286     * instance.</p>
287     *
288     * @return {@code non-null;} the word data section
289     */
290    /*package*/ MixedItemSection getTypeLists() {
291        return typeLists;
292    }
293
294    /**
295     * Gets the map section.
296     *
297     * <p>This is package-scope in order to allow the header section
298     * to query it.</p>
299     *
300     * @return {@code non-null;} the map section
301     */
302    /*package*/ MixedItemSection getMap() {
303        return map;
304    }
305
306    /**
307     * Gets the string identifiers section.
308     *
309     * <p>This is package-scope in order to allow
310     * the various {@link Item} instances to add items to the
311     * instance.</p>
312     *
313     * @return {@code non-null;} the string identifiers section
314     */
315    /*package*/ StringIdsSection getStringIds() {
316        return stringIds;
317    }
318
319    /**
320     * Gets the class definitions section.
321     *
322     * <p>This is package-scope in order to allow
323     * the various {@link Item} instances to add items to the
324     * instance.</p>
325     *
326     * @return {@code non-null;} the class definitions section
327     */
328    /*package*/ ClassDefsSection getClassDefs() {
329        return classDefs;
330    }
331
332    /**
333     * Gets the class data section.
334     *
335     * <p>This is package-scope in order to allow
336     * the various {@link Item} instances to add items to the
337     * instance.</p>
338     *
339     * @return {@code non-null;} the class data section
340     */
341    /*package*/ MixedItemSection getClassData() {
342        return classData;
343    }
344
345    /**
346     * Gets the type identifiers section.
347     *
348     * <p>This is package-scope in order to allow
349     * the various {@link Item} instances to add items to the
350     * instance.</p>
351     *
352     * @return {@code non-null;} the class identifiers section
353     */
354    /*package*/ TypeIdsSection getTypeIds() {
355        return typeIds;
356    }
357
358    /**
359     * Gets the prototype identifiers section.
360     *
361     * <p>This is package-scope in order to allow
362     * the various {@link Item} instances to add items to the
363     * instance.</p>
364     *
365     * @return {@code non-null;} the prototype identifiers section
366     */
367    /*package*/ ProtoIdsSection getProtoIds() {
368        return protoIds;
369    }
370
371    /**
372     * Gets the field identifiers section.
373     *
374     * <p>This is package-scope in order to allow
375     * the various {@link Item} instances to add items to the
376     * instance.</p>
377     *
378     * @return {@code non-null;} the field identifiers section
379     */
380    /*package*/ FieldIdsSection getFieldIds() {
381        return fieldIds;
382    }
383
384    /**
385     * Gets the method identifiers section.
386     *
387     * <p>This is package-scope in order to allow
388     * the various {@link Item} instances to add items to the
389     * instance.</p>
390     *
391     * @return {@code non-null;} the method identifiers section
392     */
393    /*package*/ MethodIdsSection getMethodIds() {
394        return methodIds;
395    }
396
397    /**
398     * Gets the byte data section.
399     *
400     * <p>This is package-scope in order to allow
401     * the various {@link Item} instances to add items to the
402     * instance.</p>
403     *
404     * @return {@code non-null;} the byte data section
405     */
406    /*package*/ MixedItemSection getByteData() {
407        return byteData;
408    }
409
410    /**
411     * Gets the first section of the file that is to be considered
412     * part of the data section.
413     *
414     * <p>This is package-scope in order to allow the header section
415     * to query it.</p>
416     *
417     * @return {@code non-null;} the section
418     */
419    /*package*/ Section getFirstDataSection() {
420        return wordData;
421    }
422
423    /**
424     * Gets the last section of the file that is to be considered
425     * part of the data section.
426     *
427     * <p>This is package-scope in order to allow the header section
428     * to query it.</p>
429     *
430     * @return {@code non-null;} the section
431     */
432    /*package*/ Section getLastDataSection() {
433        return map;
434    }
435
436    /**
437     * Interns the given constant in the appropriate section of this
438     * instance, or do nothing if the given constant isn't the sort
439     * that should be interned.
440     *
441     * @param cst {@code non-null;} constant to possibly intern
442     */
443    /*package*/ void internIfAppropriate(Constant cst) {
444        if (cst instanceof CstString) {
445            stringIds.intern((CstString) cst);
446        } else if (cst instanceof CstType) {
447            typeIds.intern((CstType) cst);
448        } else if (cst instanceof CstBaseMethodRef) {
449            methodIds.intern((CstBaseMethodRef) cst);
450        } else if (cst instanceof CstFieldRef) {
451            fieldIds.intern((CstFieldRef) cst);
452        } else if (cst instanceof CstEnumRef) {
453            fieldIds.intern(((CstEnumRef) cst).getFieldRef());
454        } else if (cst == null) {
455            throw new NullPointerException("cst == null");
456        }
457    }
458
459    /**
460     * Gets the {@link IndexedItem} corresponding to the given constant,
461     * if it is a constant that has such a correspondence, or return
462     * {@code null} if it isn't such a constant. This will throw
463     * an exception if the given constant <i>should</i> have been found
464     * but wasn't.
465     *
466     * @param cst {@code non-null;} the constant to look up
467     * @return {@code null-ok;} its corresponding item, if it has a corresponding
468     * item, or {@code null} if it's not that sort of constant
469     */
470    /*package*/ IndexedItem findItemOrNull(Constant cst) {
471        IndexedItem item;
472
473        if (cst instanceof CstString) {
474            return stringIds.get(cst);
475        } else if (cst instanceof CstType) {
476            return typeIds.get(cst);
477        } else if (cst instanceof CstBaseMethodRef) {
478            return methodIds.get(cst);
479        } else if (cst instanceof CstFieldRef) {
480            return fieldIds.get(cst);
481        } else {
482            return null;
483        }
484    }
485
486    /**
487     * Returns the contents of this instance as a {@code .dex} file,
488     * in a {@link ByteArrayAnnotatedOutput} instance.
489     *
490     * @param annotate whether or not to keep annotations
491     * @param verbose if annotating, whether to be verbose
492     * @return {@code non-null;} a {@code .dex} file for this instance
493     */
494    private ByteArrayAnnotatedOutput toDex0(boolean annotate,
495            boolean verbose) {
496        /*
497         * The following is ordered so that the prepare() calls which
498         * add items happen before the calls to the sections that get
499         * added to.
500         */
501
502        classDefs.prepare();
503        classData.prepare();
504        wordData.prepare();
505        byteData.prepare();
506        methodIds.prepare();
507        fieldIds.prepare();
508        protoIds.prepare();
509        typeLists.prepare();
510        typeIds.prepare();
511        stringIds.prepare();
512        stringData.prepare();
513        header.prepare();
514
515        // Place the sections within the file.
516
517        int count = sections.length;
518        int offset = 0;
519
520        for (int i = 0; i < count; i++) {
521            Section one = sections[i];
522            int placedAt = one.setFileOffset(offset);
523            if (placedAt < offset) {
524                throw new RuntimeException("bogus placement for section " + i);
525            }
526
527            try {
528                if (one == map) {
529                    /*
530                     * Inform the map of all the sections, and add it
531                     * to the file. This can only be done after all
532                     * the other items have been sorted and placed.
533                     */
534                    MapItem.addMap(sections, map);
535                    map.prepare();
536                }
537
538                if (one instanceof MixedItemSection) {
539                    /*
540                     * Place the items of a MixedItemSection that just
541                     * got placed.
542                     */
543                    ((MixedItemSection) one).placeItems();
544                }
545
546                offset = placedAt + one.writeSize();
547            } catch (RuntimeException ex) {
548                throw ExceptionWithContext.withContext(ex,
549                        "...while writing section " + i);
550            }
551        }
552
553        // Write out all the sections.
554
555        fileSize = offset;
556        byte[] barr = new byte[fileSize];
557        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr);
558
559        if (annotate) {
560            out.enableAnnotations(dumpWidth, verbose);
561        }
562
563        for (int i = 0; i < count; i++) {
564            try {
565                Section one = sections[i];
566                int zeroCount = one.getFileOffset() - out.getCursor();
567                if (zeroCount < 0) {
568                    throw new ExceptionWithContext("excess write of " +
569                            (-zeroCount));
570                }
571                out.writeZeroes(one.getFileOffset() - out.getCursor());
572                one.writeTo(out);
573            } catch (RuntimeException ex) {
574                ExceptionWithContext ec;
575                if (ex instanceof ExceptionWithContext) {
576                    ec = (ExceptionWithContext) ex;
577                } else {
578                    ec = new ExceptionWithContext(ex);
579                }
580                ec.addContext("...while writing section " + i);
581                throw ec;
582            }
583        }
584
585        if (out.getCursor() != fileSize) {
586            throw new RuntimeException("foreshortened write");
587        }
588
589        // Perform final bookkeeping.
590
591        calcSignature(barr);
592        calcChecksum(barr);
593
594        if (annotate) {
595            wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM,
596                    "\nmethod code index:\n\n");
597            getStatistics().writeAnnotation(out);
598            out.finishAnnotating();
599        }
600
601        return out;
602    }
603
604    /**
605     * Generates and returns statistics for all the items in the file.
606     *
607     * @return {@code non-null;} the statistics
608     */
609    public Statistics getStatistics() {
610        Statistics stats = new Statistics();
611
612        for (Section s : sections) {
613            stats.addAll(s);
614        }
615
616        return stats;
617    }
618
619    /**
620     * Calculates the signature for the {@code .dex} file in the
621     * given array, and modify the array to contain it.
622     *
623     * @param bytes {@code non-null;} the bytes of the file
624     */
625    private static void calcSignature(byte[] bytes) {
626        MessageDigest md;
627
628        try {
629            md = MessageDigest.getInstance("SHA-1");
630        } catch (NoSuchAlgorithmException ex) {
631            throw new RuntimeException(ex);
632        }
633
634        md.update(bytes, 32, bytes.length - 32);
635
636        try {
637            int amt = md.digest(bytes, 12, 20);
638            if (amt != 20) {
639                throw new RuntimeException("unexpected digest write: " + amt +
640                                           " bytes");
641            }
642        } catch (DigestException ex) {
643            throw new RuntimeException(ex);
644        }
645    }
646
647    /**
648     * Calculates the checksum for the {@code .dex} file in the
649     * given array, and modify the array to contain it.
650     *
651     * @param bytes {@code non-null;} the bytes of the file
652     */
653    private static void calcChecksum(byte[] bytes) {
654        Adler32 a32 = new Adler32();
655
656        a32.update(bytes, 12, bytes.length - 12);
657
658        int sum = (int) a32.getValue();
659
660        bytes[8]  = (byte) sum;
661        bytes[9]  = (byte) (sum >> 8);
662        bytes[10] = (byte) (sum >> 16);
663        bytes[11] = (byte) (sum >> 24);
664    }
665}
666