diff options
Diffstat (limited to 'dx/src/com/android/dx/dex/file/DexFile.java')
-rw-r--r-- | dx/src/com/android/dx/dex/file/DexFile.java | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/dex/file/DexFile.java b/dx/src/com/android/dx/dex/file/DexFile.java new file mode 100644 index 0000000..1cc9358 --- /dev/null +++ b/dx/src/com/android/dx/dex/file/DexFile.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.rop.cst.CstEnumRef; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.CstUtf8; +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArrayAnnotatedOutput; +import com.android.dx.util.ExceptionWithContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Adler32; + +import static com.android.dx.dex.file.MixedItemSection.SortType; + +/** + * Representation of an entire {@code .dex} (Dalvik EXecutable) + * file, which itself consists of a set of Dalvik classes. + */ +public final class DexFile { + /** {@code non-null;} word data section */ + private final MixedItemSection wordData; + + /** + * {@code non-null;} type lists section. This is word data, but separating + * it from {@link #wordData} helps break what would otherwise be a + * circular dependency between the that and {@link #protoIds}. + */ + private final MixedItemSection typeLists; + + /** + * {@code non-null;} map section. The map needs to be in a section by itself + * for the self-reference mechanics to work in a reasonably + * straightforward way. See {@link MapItem#addMap} for more detail. + */ + private final MixedItemSection map; + + /** {@code non-null;} string data section */ + private final MixedItemSection stringData; + + /** {@code non-null;} string identifiers section */ + private final StringIdsSection stringIds; + + /** {@code non-null;} type identifiers section */ + private final TypeIdsSection typeIds; + + /** {@code non-null;} prototype identifiers section */ + private final ProtoIdsSection protoIds; + + /** {@code non-null;} field identifiers section */ + private final FieldIdsSection fieldIds; + + /** {@code non-null;} method identifiers section */ + private final MethodIdsSection methodIds; + + /** {@code non-null;} class definitions section */ + private final ClassDefsSection classDefs; + + /** {@code non-null;} class data section */ + private final MixedItemSection classData; + + /** {@code non-null;} byte data section */ + private final MixedItemSection byteData; + + /** {@code non-null;} file header */ + private final HeaderSection header; + + /** + * {@code non-null;} array of sections in the order they will appear in the + * final output file + */ + private final Section[] sections; + + /** {@code >= -1;} total file size or {@code -1} if unknown */ + private int fileSize; + + /** {@code >= 40;} maximum width of the file dump */ + private int dumpWidth; + + /** + * Constructs an instance. It is initially empty. + */ + public DexFile() { + header = new HeaderSection(this); + typeLists = new MixedItemSection(null, this, 4, SortType.NONE); + wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE); + stringData = + new MixedItemSection("string_data", this, 1, SortType.INSTANCE); + classData = new MixedItemSection(null, this, 1, SortType.NONE); + byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE); + stringIds = new StringIdsSection(this); + typeIds = new TypeIdsSection(this); + protoIds = new ProtoIdsSection(this); + fieldIds = new FieldIdsSection(this); + methodIds = new MethodIdsSection(this); + classDefs = new ClassDefsSection(this); + map = new MixedItemSection("map", this, 4, SortType.NONE); + + /* + * This is the list of sections in the order they appear in + * the final output. + */ + sections = new Section[] { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, + classDefs, wordData, typeLists, stringData, byteData, + classData, map }; + + fileSize = -1; + dumpWidth = 79; + } + + /** + * Adds a class to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class to add + */ + public void add(ClassDefItem clazz) { + classDefs.add(clazz); + } + + /** + * Gets the class definition with the given name, if any. + * + * @param name {@code non-null;} the class name to look for + * @return {@code null-ok;} the class with the given name, or {@code null} + * if there is no such class + */ + public ClassDefItem getClassOrNull(String name) { + try { + Type type = Type.internClassName(name); + return (ClassDefItem) classDefs.get(new CstType(type)); + } catch (IllegalArgumentException ex) { + // Translate exception, per contract. + return null; + } + } + + /** + * Writes the contents of this instance as either a binary or a + * human-readable form, or both. + * + * @param out {@code null-ok;} where to write to + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + */ + public void writeTo(OutputStream out, Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (out != null) { + out.write(result.getArray()); + } + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in {@code byte[]} form. + * + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + * @return {@code non-null;} a {@code .dex} file for this instance + */ + public byte[] toDex(Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + + return result.getArray(); + } + + /** + * Sets the maximum width of the human-oriented dump of the instance. + * + * @param dumpWidth {@code >= 40;} the width + */ + public void setDumpWidth(int dumpWidth) { + if (dumpWidth < 40) { + throw new IllegalArgumentException("dumpWidth < 40"); + } + + this.dumpWidth = dumpWidth; + } + + /** + * Gets the total file size, if known. + * + * <p>This is package-scope in order to allow + * the {@link HeaderSection} to set itself up properly.</p> + * + * @return {@code >= 0;} the total file size + * @throws RuntimeException thrown if the file size is not yet known + */ + /*package*/ int getFileSize() { + if (fileSize < 0) { + throw new RuntimeException("file size not yet known"); + } + + return fileSize; + } + + /** + * Gets the string data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the string data section + */ + /*package*/ MixedItemSection getStringData() { + return stringData; + } + + /** + * Gets the word data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getWordData() { + return wordData; + } + + /** + * Gets the type lists section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getTypeLists() { + return typeLists; + } + + /** + * Gets the map section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the map section + */ + /*package*/ MixedItemSection getMap() { + return map; + } + + /** + * Gets the string identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the string identifiers section + */ + /*package*/ StringIdsSection getStringIds() { + return stringIds; + } + + /** + * Gets the class definitions section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class definitions section + */ + /*package*/ ClassDefsSection getClassDefs() { + return classDefs; + } + + /** + * Gets the class data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class data section + */ + /*package*/ MixedItemSection getClassData() { + return classData; + } + + /** + * Gets the type identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class identifiers section + */ + /*package*/ TypeIdsSection getTypeIds() { + return typeIds; + } + + /** + * Gets the prototype identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the prototype identifiers section + */ + /*package*/ ProtoIdsSection getProtoIds() { + return protoIds; + } + + /** + * Gets the field identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the field identifiers section + */ + /*package*/ FieldIdsSection getFieldIds() { + return fieldIds; + } + + /** + * Gets the method identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the method identifiers section + */ + /*package*/ MethodIdsSection getMethodIds() { + return methodIds; + } + + /** + * Gets the byte data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the byte data section + */ + /*package*/ MixedItemSection getByteData() { + return byteData; + } + + /** + * Gets the first section of the file that is to be considered + * part of the data section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the section + */ + /*package*/ Section getFirstDataSection() { + return wordData; + } + + /** + * Gets the last section of the file that is to be considered + * part of the data section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the section + */ + /*package*/ Section getLastDataSection() { + return map; + } + + /** + * Interns the given constant in the appropriate section of this + * instance, or do nothing if the given constant isn't the sort + * that should be interned. + * + * @param cst {@code non-null;} constant to possibly intern + */ + /*package*/ void internIfAppropriate(Constant cst) { + if (cst instanceof CstString) { + stringIds.intern((CstString) cst); + } else if (cst instanceof CstUtf8) { + stringIds.intern((CstUtf8) cst); + } else if (cst instanceof CstType) { + typeIds.intern((CstType) cst); + } else if (cst instanceof CstBaseMethodRef) { + methodIds.intern((CstBaseMethodRef) cst); + } else if (cst instanceof CstFieldRef) { + fieldIds.intern((CstFieldRef) cst); + } else if (cst instanceof CstEnumRef) { + fieldIds.intern(((CstEnumRef) cst).getFieldRef()); + } else if (cst == null) { + throw new NullPointerException("cst == null"); + } + } + + /** + * Gets the {@link IndexedItem} corresponding to the given constant, + * if it is a constant that has such a correspondence, or return + * {@code null} if it isn't such a constant. This will throw + * an exception if the given constant <i>should</i> have been found + * but wasn't. + * + * @param cst {@code non-null;} the constant to look up + * @return {@code null-ok;} its corresponding item, if it has a corresponding + * item, or {@code null} if it's not that sort of constant + */ + /*package*/ IndexedItem findItemOrNull(Constant cst) { + IndexedItem item; + + if (cst instanceof CstString) { + return stringIds.get(cst); + } else if (cst instanceof CstType) { + return typeIds.get(cst); + } else if (cst instanceof CstBaseMethodRef) { + return methodIds.get(cst); + } else if (cst instanceof CstFieldRef) { + return fieldIds.get(cst); + } else { + return null; + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in a {@link ByteArrayAnnotatedOutput} instance. + * + * @param annotate whether or not to keep annotations + * @param verbose if annotating, whether to be verbose + * @return {@code non-null;} a {@code .dex} file for this instance + */ + private ByteArrayAnnotatedOutput toDex0(boolean annotate, + boolean verbose) { + /* + * The following is ordered so that the prepare() calls which + * add items happen before the calls to the sections that get + * added to. + */ + + classDefs.prepare(); + classData.prepare(); + wordData.prepare(); + byteData.prepare(); + methodIds.prepare(); + fieldIds.prepare(); + protoIds.prepare(); + typeLists.prepare(); + typeIds.prepare(); + stringIds.prepare(); + stringData.prepare(); + header.prepare(); + + // Place the sections within the file. + + int count = sections.length; + int offset = 0; + + for (int i = 0; i < count; i++) { + Section one = sections[i]; + int placedAt = one.setFileOffset(offset); + if (placedAt < offset) { + throw new RuntimeException("bogus placement for section " + i); + } + + try { + if (one == map) { + /* + * Inform the map of all the sections, and add it + * to the file. This can only be done after all + * the other items have been sorted and placed. + */ + MapItem.addMap(sections, map); + map.prepare(); + } + + if (one instanceof MixedItemSection) { + /* + * Place the items of a MixedItemSection that just + * got placed. + */ + ((MixedItemSection) one).placeItems(); + } + + offset = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing section " + i); + } + } + + // Write out all the sections. + + fileSize = offset; + byte[] barr = new byte[fileSize]; + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr); + + if (annotate) { + out.enableAnnotations(dumpWidth, verbose); + } + + for (int i = 0; i < count; i++) { + try { + Section one = sections[i]; + int zeroCount = one.getFileOffset() - out.getCursor(); + if (zeroCount < 0) { + throw new ExceptionWithContext("excess write of " + + (-zeroCount)); + } + out.writeZeroes(one.getFileOffset() - out.getCursor()); + one.writeTo(out); + } catch (RuntimeException ex) { + ExceptionWithContext ec; + if (ex instanceof ExceptionWithContext) { + ec = (ExceptionWithContext) ex; + } else { + ec = new ExceptionWithContext(ex); + } + ec.addContext("...while writing section " + i); + throw ec; + } + } + + if (out.getCursor() != fileSize) { + throw new RuntimeException("foreshortened write"); + } + + // Perform final bookkeeping. + + calcSignature(barr); + calcChecksum(barr); + + if (annotate) { + wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM, + "\nmethod code index:\n\n"); + getStatistics().writeAnnotation(out); + out.finishAnnotating(); + } + + return out; + } + + /** + * Generates and returns statistics for all the items in the file. + * + * @return {@code non-null;} the statistics + */ + public Statistics getStatistics() { + Statistics stats = new Statistics(); + + for (Section s : sections) { + stats.addAll(s); + } + + return stats; + } + + /** + * Calculates the signature for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcSignature(byte[] bytes) { + MessageDigest md; + + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + + md.update(bytes, 32, bytes.length - 32); + + try { + int amt = md.digest(bytes, 12, 20); + if (amt != 20) { + throw new RuntimeException("unexpected digest write: " + amt + + " bytes"); + } + } catch (DigestException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Calculates the checksum for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcChecksum(byte[] bytes) { + Adler32 a32 = new Adler32(); + + a32.update(bytes, 12, bytes.length - 12); + + int sum = (int) a32.getValue(); + + bytes[8] = (byte) sum; + bytes[9] = (byte) (sum >> 8); + bytes[10] = (byte) (sum >> 16); + bytes[11] = (byte) (sum >> 24); + } +} |