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