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