| /* |
| * Copyright 2013, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.dexlib2.writer; |
| |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Ordering; |
| import org.jf.dexlib2.AccessFlags; |
| import org.jf.dexlib2.Opcode; |
| import org.jf.dexlib2.Opcodes; |
| import org.jf.dexlib2.ReferenceType; |
| import org.jf.dexlib2.base.BaseAnnotation; |
| import org.jf.dexlib2.base.BaseAnnotationElement; |
| import org.jf.dexlib2.builder.MutableMethodImplementation; |
| import org.jf.dexlib2.builder.instruction.BuilderInstruction31c; |
| import org.jf.dexlib2.dexbacked.raw.*; |
| import org.jf.dexlib2.iface.Annotation; |
| import org.jf.dexlib2.iface.ExceptionHandler; |
| import org.jf.dexlib2.iface.TryBlock; |
| import org.jf.dexlib2.iface.debug.DebugItem; |
| import org.jf.dexlib2.iface.debug.LineNumber; |
| import org.jf.dexlib2.iface.instruction.Instruction; |
| import org.jf.dexlib2.iface.instruction.OneRegisterInstruction; |
| import org.jf.dexlib2.iface.instruction.ReferenceInstruction; |
| import org.jf.dexlib2.iface.instruction.VariableRegisterInstruction; |
| import org.jf.dexlib2.iface.instruction.formats.*; |
| import org.jf.dexlib2.iface.reference.*; |
| import org.jf.dexlib2.util.InstructionUtil; |
| import org.jf.dexlib2.util.MethodUtil; |
| import org.jf.dexlib2.util.ReferenceUtil; |
| import org.jf.dexlib2.writer.io.DeferredOutputStream; |
| import org.jf.dexlib2.writer.io.DeferredOutputStreamFactory; |
| import org.jf.dexlib2.writer.io.DexDataStore; |
| import org.jf.dexlib2.writer.io.MemoryDeferredOutputStream; |
| import org.jf.dexlib2.writer.util.TryListBuilder; |
| import org.jf.util.CollectionUtils; |
| import org.jf.util.ExceptionWithContext; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.*; |
| import java.util.Map.Entry; |
| import java.util.zip.Adler32; |
| |
| public abstract class DexWriter< |
| StringKey extends CharSequence, StringRef extends StringReference, TypeKey extends CharSequence, |
| TypeRef extends TypeReference, ProtoRefKey extends MethodProtoReference, |
| FieldRefKey extends FieldReference, MethodRefKey extends MethodReference, |
| ClassKey extends Comparable<? super ClassKey>, |
| AnnotationKey extends Annotation, AnnotationSetKey, |
| TypeListKey, |
| FieldKey, MethodKey, |
| EncodedValue, |
| AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement, |
| StringSectionType extends StringSection<StringKey, StringRef>, |
| TypeSectionType extends TypeSection<StringKey, TypeKey, TypeRef>, |
| ProtoSectionType extends ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey>, |
| FieldSectionType extends FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey>, |
| MethodSectionType extends MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey>, |
| ClassSectionType extends ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, |
| AnnotationSetKey, EncodedValue>, |
| TypeListSectionType extends TypeListSection<TypeKey, TypeListKey>, |
| AnnotationSectionType extends AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement, |
| EncodedValue>, |
| AnnotationSetSectionType extends AnnotationSetSection<AnnotationKey, AnnotationSetKey>> { |
| public static final int NO_INDEX = -1; |
| public static final int NO_OFFSET = 0; |
| |
| protected final Opcodes opcodes; |
| |
| protected int stringIndexSectionOffset = NO_OFFSET; |
| protected int typeSectionOffset = NO_OFFSET; |
| protected int protoSectionOffset = NO_OFFSET; |
| protected int fieldSectionOffset = NO_OFFSET; |
| protected int methodSectionOffset = NO_OFFSET; |
| protected int classIndexSectionOffset = NO_OFFSET; |
| |
| protected int stringDataSectionOffset = NO_OFFSET; |
| protected int classDataSectionOffset = NO_OFFSET; |
| protected int typeListSectionOffset = NO_OFFSET; |
| protected int encodedArraySectionOffset = NO_OFFSET; |
| protected int annotationSectionOffset = NO_OFFSET; |
| protected int annotationSetSectionOffset = NO_OFFSET; |
| protected int annotationSetRefSectionOffset = NO_OFFSET; |
| protected int annotationDirectorySectionOffset = NO_OFFSET; |
| protected int debugSectionOffset = NO_OFFSET; |
| protected int codeSectionOffset = NO_OFFSET; |
| protected int mapSectionOffset = NO_OFFSET; |
| |
| protected int numEncodedArrayItems = 0; |
| protected int numAnnotationSetRefItems = 0; |
| protected int numAnnotationDirectoryItems = 0; |
| protected int numDebugInfoItems = 0; |
| protected int numCodeItemItems = 0; |
| protected int numClassDataItems = 0; |
| |
| public final StringSectionType stringSection; |
| public final TypeSectionType typeSection; |
| public final ProtoSectionType protoSection; |
| public final FieldSectionType fieldSection; |
| public final MethodSectionType methodSection; |
| public final ClassSectionType classSection; |
| |
| public final TypeListSectionType typeListSection; |
| public final AnnotationSectionType annotationSection; |
| public final AnnotationSetSectionType annotationSetSection; |
| |
| protected DexWriter(Opcodes opcodes) { |
| this.opcodes = opcodes; |
| |
| SectionProvider sectionProvider = getSectionProvider(); |
| this.stringSection = sectionProvider.getStringSection(); |
| this.typeSection = sectionProvider.getTypeSection(); |
| this.protoSection = sectionProvider.getProtoSection(); |
| this.fieldSection = sectionProvider.getFieldSection(); |
| this.methodSection = sectionProvider.getMethodSection(); |
| this.classSection = sectionProvider.getClassSection(); |
| this.typeListSection = sectionProvider.getTypeListSection(); |
| this.annotationSection = sectionProvider.getAnnotationSection(); |
| this.annotationSetSection = sectionProvider.getAnnotationSetSection(); |
| } |
| |
| @Nonnull protected abstract SectionProvider getSectionProvider(); |
| |
| protected abstract void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer, |
| @Nonnull EncodedValue encodedValue) throws IOException; |
| |
| private static Comparator<Map.Entry> toStringKeyComparator = |
| new Comparator<Map.Entry>() { |
| @Override public int compare(Entry o1, Entry o2) { |
| return o1.getKey().toString().compareTo(o2.getKey().toString()); |
| } |
| }; |
| |
| private static <T extends Comparable<? super T>> Comparator<Map.Entry<? extends T, ?>> comparableKeyComparator() { |
| return new Comparator<Entry<? extends T, ?>>() { |
| @Override public int compare(Entry<? extends T, ?> o1, Entry<? extends T, ?> o2) { |
| return o1.getKey().compareTo(o2.getKey()); |
| } |
| }; |
| } |
| |
| protected class InternalEncodedValueWriter extends EncodedValueWriter<StringKey, TypeKey, FieldRefKey, MethodRefKey, |
| AnnotationElement, EncodedValue> { |
| private InternalEncodedValueWriter(@Nonnull DexDataWriter writer) { |
| super(writer, stringSection, typeSection, fieldSection, methodSection, annotationSection); |
| } |
| |
| @Override protected void writeEncodedValue(@Nonnull EncodedValue encodedValue) throws IOException { |
| DexWriter.this.writeEncodedValue(this, encodedValue); |
| } |
| } |
| |
| private int getDataSectionOffset() { |
| return HeaderItem.ITEM_SIZE + |
| stringSection.getItemCount() * StringIdItem.ITEM_SIZE + |
| typeSection.getItemCount() * TypeIdItem.ITEM_SIZE + |
| protoSection.getItemCount() * ProtoIdItem.ITEM_SIZE + |
| fieldSection.getItemCount() * FieldIdItem.ITEM_SIZE + |
| methodSection.getItemCount() * MethodIdItem.ITEM_SIZE + |
| classSection.getItemCount() * ClassDefItem.ITEM_SIZE; |
| } |
| |
| @Nonnull |
| public List<String> getMethodReferences() { |
| List<String> methodReferences = Lists.newArrayList(); |
| for (Entry<? extends MethodRefKey, Integer> methodReference: methodSection.getItems()) { |
| methodReferences.add(ReferenceUtil.getMethodDescriptor(methodReference.getKey())); |
| } |
| return methodReferences; |
| } |
| |
| @Nonnull |
| public List<String> getFieldReferences() { |
| List<String> fieldReferences = Lists.newArrayList(); |
| for (Entry<? extends FieldRefKey, Integer> fieldReference: fieldSection.getItems()) { |
| fieldReferences.add(ReferenceUtil.getFieldDescriptor(fieldReference.getKey())); |
| } |
| return fieldReferences; |
| } |
| |
| @Nonnull |
| public List<String> getTypeReferences() { |
| List<String> classReferences = Lists.newArrayList(); |
| for (Entry<? extends TypeKey, Integer> typeReference: typeSection.getItems()) { |
| classReferences.add(typeReference.getKey().toString()); |
| } |
| return classReferences; |
| } |
| |
| /** |
| * Checks whether any of the size-sensitive constant pools have overflowed. |
| * |
| * This checks whether the type, method, field pools are larger than 64k entries. |
| * |
| * Note that even if this returns true, it may still be possible to successfully write the dex file, if the |
| * overflowed items are not referenced anywhere that uses a 16-bit index |
| * |
| * @return true if any of the size-sensitive constant pools have overflowed |
| */ |
| public boolean hasOverflowed() { |
| return methodSection.getItemCount() > (1 << 16) || |
| typeSection.getItemCount() > (1 << 16) || |
| fieldSection.getItemCount() > (1 << 16); |
| } |
| |
| public void writeTo(@Nonnull DexDataStore dest) throws IOException { |
| this.writeTo(dest, MemoryDeferredOutputStream.getFactory()); |
| } |
| |
| public void writeTo(@Nonnull DexDataStore dest, |
| @Nonnull DeferredOutputStreamFactory tempFactory) throws IOException { |
| try { |
| int dataSectionOffset = getDataSectionOffset(); |
| DexDataWriter headerWriter = outputAt(dest, 0); |
| DexDataWriter indexWriter = outputAt(dest, HeaderItem.ITEM_SIZE); |
| DexDataWriter offsetWriter = outputAt(dest, dataSectionOffset); |
| try { |
| writeStrings(indexWriter, offsetWriter); |
| writeTypes(indexWriter); |
| writeTypeLists(offsetWriter); |
| writeProtos(indexWriter); |
| writeFields(indexWriter); |
| writeMethods(indexWriter); |
| writeEncodedArrays(offsetWriter); |
| writeAnnotations(offsetWriter); |
| writeAnnotationSets(offsetWriter); |
| writeAnnotationSetRefs(offsetWriter); |
| writeAnnotationDirectories(offsetWriter); |
| writeDebugAndCodeItems(offsetWriter, tempFactory.makeDeferredOutputStream()); |
| writeClasses(indexWriter, offsetWriter); |
| writeMapItem(offsetWriter); |
| writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition()); |
| } finally { |
| headerWriter.close(); |
| indexWriter.close(); |
| offsetWriter.close(); |
| } |
| updateSignature(dest); |
| updateChecksum(dest); |
| } finally { |
| dest.close(); |
| } |
| } |
| |
| private void updateSignature(@Nonnull DexDataStore dataStore) throws IOException { |
| MessageDigest md; |
| try { |
| md = MessageDigest.getInstance("SHA-1"); |
| } catch (NoSuchAlgorithmException ex) { |
| throw new RuntimeException(ex); |
| } |
| |
| byte[] buffer = new byte[4 * 1024]; |
| InputStream input = dataStore.readAt(HeaderItem.SIGNATURE_DATA_START_OFFSET); |
| int bytesRead = input.read(buffer); |
| while (bytesRead >= 0) { |
| md.update(buffer, 0, bytesRead); |
| bytesRead = input.read(buffer); |
| } |
| |
| byte[] signature = md.digest(); |
| if (signature.length != HeaderItem.SIGNATURE_SIZE) { |
| throw new RuntimeException("unexpected digest write: " + signature.length + " bytes"); |
| } |
| |
| // write signature |
| OutputStream output = dataStore.outputAt(HeaderItem.SIGNATURE_OFFSET); |
| output.write(signature); |
| output.close(); |
| } |
| |
| private void updateChecksum(@Nonnull DexDataStore dataStore) throws IOException { |
| Adler32 a32 = new Adler32(); |
| |
| byte[] buffer = new byte[4 * 1024]; |
| InputStream input = dataStore.readAt(HeaderItem.CHECKSUM_DATA_START_OFFSET); |
| int bytesRead = input.read(buffer); |
| while (bytesRead >= 0) { |
| a32.update(buffer, 0, bytesRead); |
| bytesRead = input.read(buffer); |
| } |
| |
| // write checksum, utilizing logic in DexWriter to write the integer value properly |
| OutputStream output = dataStore.outputAt(HeaderItem.CHECKSUM_OFFSET); |
| DexDataWriter.writeInt(output, (int)a32.getValue()); |
| output.close(); |
| } |
| |
| private static DexDataWriter outputAt(DexDataStore dataStore, int filePosition) throws IOException { |
| return new DexDataWriter(dataStore.outputAt(filePosition), filePosition); |
| } |
| |
| private void writeStrings(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException { |
| stringIndexSectionOffset = indexWriter.getPosition(); |
| stringDataSectionOffset = offsetWriter.getPosition(); |
| int index = 0; |
| List<Entry<? extends StringKey, Integer>> stringEntries = Lists.newArrayList(stringSection.getItems()); |
| Collections.sort(stringEntries, toStringKeyComparator); |
| |
| for (Map.Entry<? extends StringKey, Integer> entry: stringEntries) { |
| entry.setValue(index++); |
| indexWriter.writeInt(offsetWriter.getPosition()); |
| String stringValue = entry.getKey().toString(); |
| offsetWriter.writeUleb128(stringValue.length()); |
| offsetWriter.writeString(stringValue); |
| offsetWriter.write(0); |
| } |
| } |
| |
| private void writeTypes(@Nonnull DexDataWriter writer) throws IOException { |
| typeSectionOffset = writer.getPosition(); |
| int index = 0; |
| |
| List<Map.Entry<? extends TypeKey, Integer>> typeEntries = Lists.newArrayList(typeSection.getItems()); |
| Collections.sort(typeEntries, toStringKeyComparator); |
| |
| for (Map.Entry<? extends TypeKey, Integer> entry : typeEntries) { |
| entry.setValue(index++); |
| writer.writeInt(stringSection.getItemIndex(typeSection.getString(entry.getKey()))); |
| } |
| } |
| |
| private void writeProtos(@Nonnull DexDataWriter writer) throws IOException { |
| protoSectionOffset = writer.getPosition(); |
| int index = 0; |
| |
| List<Map.Entry<? extends ProtoRefKey, Integer>> protoEntries = Lists.newArrayList(protoSection.getItems()); |
| Collections.sort(protoEntries, DexWriter.<ProtoRefKey>comparableKeyComparator()); |
| |
| for (Map.Entry<? extends ProtoRefKey, Integer> entry: protoEntries) { |
| entry.setValue(index++); |
| ProtoRefKey key = entry.getKey(); |
| writer.writeInt(stringSection.getItemIndex(protoSection.getShorty(key))); |
| writer.writeInt(typeSection.getItemIndex(protoSection.getReturnType(key))); |
| writer.writeInt(typeListSection.getNullableItemOffset(protoSection.getParameters(key))); |
| } |
| } |
| |
| private void writeFields(@Nonnull DexDataWriter writer) throws IOException { |
| fieldSectionOffset = writer.getPosition(); |
| int index = 0; |
| |
| List<Map.Entry<? extends FieldRefKey, Integer>> fieldEntries = Lists.newArrayList(fieldSection.getItems()); |
| Collections.sort(fieldEntries, DexWriter.<FieldRefKey>comparableKeyComparator()); |
| |
| for (Map.Entry<? extends FieldRefKey, Integer> entry: fieldEntries) { |
| entry.setValue(index++); |
| FieldRefKey key = entry.getKey(); |
| writer.writeUshort(typeSection.getItemIndex(fieldSection.getDefiningClass(key))); |
| writer.writeUshort(typeSection.getItemIndex(fieldSection.getFieldType(key))); |
| writer.writeInt(stringSection.getItemIndex(fieldSection.getName(key))); |
| } |
| } |
| |
| private void writeMethods(@Nonnull DexDataWriter writer) throws IOException { |
| methodSectionOffset = writer.getPosition(); |
| int index = 0; |
| |
| List<Map.Entry<? extends MethodRefKey, Integer>> methodEntries = Lists.newArrayList(methodSection.getItems()); |
| Collections.sort(methodEntries, DexWriter.<MethodRefKey>comparableKeyComparator()); |
| |
| for (Map.Entry<? extends MethodRefKey, Integer> entry: methodEntries) { |
| entry.setValue(index++); |
| MethodRefKey key = entry.getKey(); |
| writer.writeUshort(typeSection.getItemIndex(methodSection.getDefiningClass(key))); |
| writer.writeUshort(protoSection.getItemIndex(methodSection.getPrototype(key))); |
| writer.writeInt(stringSection.getItemIndex(methodSection.getName(key))); |
| } |
| } |
| |
| private void writeClasses(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException { |
| classIndexSectionOffset = indexWriter.getPosition(); |
| classDataSectionOffset = offsetWriter.getPosition(); |
| |
| List<Map.Entry<? extends ClassKey, Integer>> classEntries = Lists.newArrayList(classSection.getItems()); |
| Collections.sort(classEntries, DexWriter.<ClassKey>comparableKeyComparator()); |
| |
| int index = 0; |
| for (Map.Entry<? extends ClassKey, Integer> key: classEntries) { |
| index = writeClass(indexWriter, offsetWriter, index, key); |
| } |
| } |
| |
| /** |
| * Writes out the class_def_item and class_data_item for the given class. |
| * |
| * This will recursively write out any unwritten superclass/interface before writing the class itself, as per the |
| * dex specification. |
| * |
| * @return the index for the next class to be written |
| */ |
| private int writeClass(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter, |
| int nextIndex, @Nullable Map.Entry<? extends ClassKey, Integer> entry) throws IOException { |
| if (entry == null) { |
| // class does not exist in this dex file, cannot write it |
| return nextIndex; |
| } |
| |
| if (entry.getValue() != NO_INDEX) { |
| // class has already been written, no need to write it |
| return nextIndex; |
| } |
| |
| ClassKey key = entry.getKey(); |
| |
| // set a bogus index, to make sure we don't recurse and double-write it |
| entry.setValue(0); |
| |
| // first, try to write the superclass |
| Map.Entry<? extends ClassKey, Integer> superEntry = |
| classSection.getClassEntryByType(classSection.getSuperclass(key)); |
| nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, superEntry); |
| |
| // then, try to write interfaces |
| for (TypeKey interfaceTypeKey: typeListSection.getTypes(classSection.getInterfaces(key))) { |
| Map.Entry<? extends ClassKey, Integer> interfaceEntry = classSection.getClassEntryByType(interfaceTypeKey); |
| nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, interfaceEntry); |
| } |
| |
| // now set the index for real |
| entry.setValue(nextIndex++); |
| |
| // and finally, write the class itself |
| // first, the class_def_item |
| indexWriter.writeInt(typeSection.getItemIndex(classSection.getType(key))); |
| indexWriter.writeInt(classSection.getAccessFlags(key)); |
| indexWriter.writeInt(typeSection.getNullableItemIndex(classSection.getSuperclass(key))); |
| indexWriter.writeInt(typeListSection.getNullableItemOffset(classSection.getInterfaces(key))); |
| indexWriter.writeInt(stringSection.getNullableItemIndex(classSection.getSourceFile(key))); |
| indexWriter.writeInt(classSection.getAnnotationDirectoryOffset(key)); |
| |
| Collection<? extends FieldKey> staticFields = classSection.getSortedStaticFields(key); |
| Collection<? extends FieldKey> instanceFields = classSection.getSortedInstanceFields(key); |
| Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(key); |
| Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(key); |
| boolean classHasData = staticFields.size() > 0 || |
| instanceFields.size() > 0 || |
| directMethods.size() > 0 || |
| virtualMethods.size() > 0; |
| |
| if (classHasData) { |
| indexWriter.writeInt(offsetWriter.getPosition()); |
| } else { |
| indexWriter.writeInt(0); |
| } |
| |
| indexWriter.writeInt(classSection.getEncodedArrayOffset(key)); |
| |
| // now write the class_data_item |
| if (classHasData) { |
| numClassDataItems++; |
| |
| offsetWriter.writeUleb128(staticFields.size()); |
| offsetWriter.writeUleb128(instanceFields.size()); |
| offsetWriter.writeUleb128(directMethods.size()); |
| offsetWriter.writeUleb128(virtualMethods.size()); |
| |
| writeEncodedFields(offsetWriter, staticFields); |
| writeEncodedFields(offsetWriter, instanceFields); |
| writeEncodedMethods(offsetWriter, directMethods); |
| writeEncodedMethods(offsetWriter, virtualMethods); |
| } |
| |
| return nextIndex; |
| } |
| |
| private void writeEncodedFields(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends FieldKey> fields) |
| throws IOException { |
| int prevIndex = 0; |
| for (FieldKey key: fields) { |
| int index = fieldSection.getFieldIndex(key); |
| writer.writeUleb128(index - prevIndex); |
| writer.writeUleb128(classSection.getFieldAccessFlags(key)); |
| prevIndex = index; |
| } |
| } |
| |
| private void writeEncodedMethods(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends MethodKey> methods) |
| throws IOException { |
| int prevIndex = 0; |
| for (MethodKey key: methods) { |
| int index = methodSection.getMethodIndex(key); |
| writer.writeUleb128(index-prevIndex); |
| writer.writeUleb128(classSection.getMethodAccessFlags(key)); |
| writer.writeUleb128(classSection.getCodeItemOffset(key)); |
| prevIndex = index; |
| } |
| } |
| |
| private void writeTypeLists(@Nonnull DexDataWriter writer) throws IOException { |
| writer.align(); |
| typeListSectionOffset = writer.getPosition(); |
| for (Map.Entry<? extends TypeListKey, Integer> entry: typeListSection.getItems()) { |
| writer.align(); |
| entry.setValue(writer.getPosition()); |
| |
| Collection<? extends TypeKey> types = typeListSection.getTypes(entry.getKey()); |
| writer.writeInt(types.size()); |
| for (TypeKey typeKey: types) { |
| writer.writeUshort(typeSection.getItemIndex(typeKey)); |
| } |
| } |
| } |
| |
| private static class EncodedArrayKey<EncodedValue> { |
| @Nonnull Collection<? extends EncodedValue> elements; |
| |
| public EncodedArrayKey() { |
| } |
| |
| @Override public int hashCode() { |
| return CollectionUtils.listHashCode(elements); |
| } |
| |
| @Override public boolean equals(Object o) { |
| if (o instanceof EncodedArrayKey) { |
| EncodedArrayKey other = (EncodedArrayKey)o; |
| if (elements.size() != other.elements.size()) { |
| return false; |
| } |
| return Iterables.elementsEqual(elements, other.elements); |
| } |
| return false; |
| } |
| } |
| |
| private void writeEncodedArrays(@Nonnull DexDataWriter writer) throws IOException { |
| InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer); |
| encodedArraySectionOffset = writer.getPosition(); |
| |
| HashMap<EncodedArrayKey<EncodedValue>, Integer> internedItems = Maps.newHashMap(); |
| EncodedArrayKey<EncodedValue> key = new EncodedArrayKey<EncodedValue>(); |
| |
| for (ClassKey classKey: classSection.getSortedClasses()) { |
| Collection <? extends EncodedValue> elements = classSection.getStaticInitializers(classKey); |
| if (elements != null && elements.size() > 0) { |
| key.elements = elements; |
| Integer prev = internedItems.get(key); |
| if (prev != null) { |
| classSection.setEncodedArrayOffset(classKey, prev); |
| } else { |
| int offset = writer.getPosition(); |
| internedItems.put(key, offset); |
| classSection.setEncodedArrayOffset(classKey, offset); |
| key = new EncodedArrayKey<EncodedValue>(); |
| |
| numEncodedArrayItems++; |
| |
| writer.writeUleb128(elements.size()); |
| for (EncodedValue value: elements) { |
| writeEncodedValue(encodedValueWriter, value); |
| } |
| } |
| } |
| } |
| } |
| |
| private void writeAnnotations(@Nonnull DexDataWriter writer) throws IOException { |
| InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer); |
| |
| annotationSectionOffset = writer.getPosition(); |
| for (Map.Entry<? extends AnnotationKey, Integer> entry: annotationSection.getItems()) { |
| entry.setValue(writer.getPosition()); |
| |
| AnnotationKey key = entry.getKey(); |
| |
| writer.writeUbyte(annotationSection.getVisibility(key)); |
| writer.writeUleb128(typeSection.getItemIndex(annotationSection.getType(key))); |
| |
| Collection<? extends AnnotationElement> elements = Ordering.from(BaseAnnotationElement.BY_NAME) |
| .immutableSortedCopy(annotationSection.getElements(key)); |
| |
| writer.writeUleb128(elements.size()); |
| |
| for (AnnotationElement element: elements) { |
| writer.writeUleb128(stringSection.getItemIndex(annotationSection.getElementName(element))); |
| writeEncodedValue(encodedValueWriter, annotationSection.getElementValue(element)); |
| } |
| } |
| } |
| |
| private void writeAnnotationSets(@Nonnull DexDataWriter writer) throws IOException { |
| writer.align(); |
| annotationSetSectionOffset = writer.getPosition(); |
| if (shouldCreateEmptyAnnotationSet()) { |
| writer.writeInt(0); |
| } |
| for (Map.Entry<? extends AnnotationSetKey, Integer> entry: annotationSetSection.getItems()) { |
| Collection<? extends AnnotationKey> annotations = Ordering.from(BaseAnnotation.BY_TYPE) |
| .immutableSortedCopy(annotationSetSection.getAnnotations(entry.getKey())); |
| |
| writer.align(); |
| entry.setValue(writer.getPosition()); |
| writer.writeInt(annotations.size()); |
| for (AnnotationKey annotationKey: annotations) { |
| writer.writeInt(annotationSection.getItemOffset(annotationKey)); |
| } |
| } |
| } |
| |
| private void writeAnnotationSetRefs(@Nonnull DexDataWriter writer) throws IOException { |
| writer.align(); |
| annotationSetRefSectionOffset = writer.getPosition(); |
| HashMap<List<? extends AnnotationSetKey>, Integer> internedItems = Maps.newHashMap(); |
| |
| for (ClassKey classKey: classSection.getSortedClasses()) { |
| for (MethodKey methodKey: classSection.getSortedMethods(classKey)) { |
| List<? extends AnnotationSetKey> parameterAnnotations = classSection.getParameterAnnotations(methodKey); |
| if (parameterAnnotations != null) { |
| Integer prev = internedItems.get(parameterAnnotations); |
| if (prev != null) { |
| classSection.setAnnotationSetRefListOffset(methodKey, prev); |
| } else { |
| writer.align(); |
| int position = writer.getPosition(); |
| classSection.setAnnotationSetRefListOffset(methodKey, position); |
| internedItems.put(parameterAnnotations, position); |
| |
| numAnnotationSetRefItems++; |
| |
| writer.writeInt(parameterAnnotations.size()); |
| for (AnnotationSetKey annotationSetKey: parameterAnnotations) { |
| if (annotationSetSection.getAnnotations(annotationSetKey).size() > 0) { |
| writer.writeInt(annotationSetSection.getItemOffset(annotationSetKey)); |
| } else if (shouldCreateEmptyAnnotationSet()) { |
| writer.writeInt(annotationSetSectionOffset); |
| } else { |
| writer.writeInt(NO_OFFSET); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void writeAnnotationDirectories(@Nonnull DexDataWriter writer) throws IOException { |
| writer.align(); |
| annotationDirectorySectionOffset = writer.getPosition(); |
| HashMap<AnnotationSetKey, Integer> internedItems = Maps.newHashMap(); |
| |
| ByteBuffer tempBuffer = ByteBuffer.allocate(65536); |
| tempBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| |
| for (ClassKey key: classSection.getSortedClasses()) { |
| // first, we write the field/method/parameter items to a temporary buffer, so that we can get a count |
| // of each type, and determine if we even need to write an annotation directory for this class |
| |
| Collection<? extends FieldKey> fields = classSection.getSortedFields(key); |
| Collection<? extends MethodKey> methods = classSection.getSortedMethods(key); |
| |
| // this is how much space we'll need if every field and method has annotations. |
| int maxSize = fields.size() * 8 + methods.size() * 16; |
| if (maxSize > tempBuffer.capacity()) { |
| tempBuffer = ByteBuffer.allocate(maxSize); |
| tempBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| } |
| |
| tempBuffer.clear(); |
| |
| int fieldAnnotations = 0; |
| int methodAnnotations = 0; |
| int parameterAnnotations = 0; |
| |
| for (FieldKey field: fields) { |
| AnnotationSetKey fieldAnnotationsKey = classSection.getFieldAnnotations(field); |
| if (fieldAnnotationsKey != null) { |
| fieldAnnotations++; |
| tempBuffer.putInt(fieldSection.getFieldIndex(field)); |
| tempBuffer.putInt(annotationSetSection.getItemOffset(fieldAnnotationsKey)); |
| } |
| } |
| |
| for (MethodKey method: methods) { |
| AnnotationSetKey methodAnnotationsKey = classSection.getMethodAnnotations(method); |
| if (methodAnnotationsKey != null) { |
| methodAnnotations++; |
| tempBuffer.putInt(methodSection.getMethodIndex(method)); |
| tempBuffer.putInt(annotationSetSection.getItemOffset(methodAnnotationsKey)); |
| } |
| } |
| |
| for (MethodKey method: methods) { |
| int offset = classSection.getAnnotationSetRefListOffset(method); |
| if (offset != DexWriter.NO_OFFSET) { |
| parameterAnnotations++; |
| tempBuffer.putInt(methodSection.getMethodIndex(method)); |
| tempBuffer.putInt(offset); |
| } |
| } |
| |
| // now, we finally know how many field/method/parameter annotations were written to the temp buffer |
| |
| AnnotationSetKey classAnnotationKey = classSection.getClassAnnotations(key); |
| if (fieldAnnotations == 0 && methodAnnotations == 0 && parameterAnnotations == 0) { |
| if (classAnnotationKey != null) { |
| // This is an internable directory. Let's see if we've already written one like it |
| Integer directoryOffset = internedItems.get(classAnnotationKey); |
| if (directoryOffset != null) { |
| classSection.setAnnotationDirectoryOffset(key, directoryOffset); |
| continue; |
| } else { |
| internedItems.put(classAnnotationKey, writer.getPosition()); |
| } |
| } else { |
| continue; |
| } |
| } |
| |
| // yep, we need to write it out |
| numAnnotationDirectoryItems++; |
| classSection.setAnnotationDirectoryOffset(key, writer.getPosition()); |
| |
| writer.writeInt(annotationSetSection.getNullableItemOffset(classAnnotationKey)); |
| writer.writeInt(fieldAnnotations); |
| writer.writeInt(methodAnnotations); |
| writer.writeInt(parameterAnnotations); |
| writer.write(tempBuffer.array(), 0, tempBuffer.position()); |
| } |
| } |
| |
| private static class CodeItemOffset<MethodKey> { |
| @Nonnull MethodKey method; |
| int codeOffset; |
| |
| private CodeItemOffset(@Nonnull MethodKey method, int codeOffset) { |
| this.codeOffset = codeOffset; |
| this.method = method; |
| } |
| } |
| |
| private void writeDebugAndCodeItems(@Nonnull DexDataWriter offsetWriter, |
| @Nonnull DeferredOutputStream temp) throws IOException { |
| ByteArrayOutputStream ehBuf = new ByteArrayOutputStream(); |
| debugSectionOffset = offsetWriter.getPosition(); |
| DebugWriter<StringKey, TypeKey> debugWriter = |
| new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, offsetWriter); |
| |
| DexDataWriter codeWriter = new DexDataWriter(temp, 0); |
| |
| List<CodeItemOffset<MethodKey>> codeOffsets = Lists.newArrayList(); |
| |
| for (ClassKey classKey: classSection.getSortedClasses()) { |
| Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey); |
| Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey); |
| |
| Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods); |
| |
| for (MethodKey methodKey: methods) { |
| List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = |
| classSection.getTryBlocks(methodKey); |
| Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey); |
| Iterable<? extends DebugItem> debugItems = classSection.getDebugItems(methodKey); |
| |
| if (instructions != null && stringSection.hasJumboIndexes()) { |
| boolean needsFix = false; |
| for (Instruction instruction: instructions) { |
| if (instruction.getOpcode() == Opcode.CONST_STRING) { |
| if (stringSection.getItemIndex( |
| (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) { |
| needsFix = true; |
| break; |
| } |
| } |
| } |
| |
| if (needsFix) { |
| MutableMethodImplementation mutableMethodImplementation = |
| classSection.makeMutableMethodImplementation(methodKey); |
| fixInstructions(mutableMethodImplementation); |
| |
| instructions = mutableMethodImplementation.getInstructions(); |
| tryBlocks = mutableMethodImplementation.getTryBlocks(); |
| debugItems = mutableMethodImplementation.getDebugItems(); |
| } |
| } |
| |
| int debugItemOffset = writeDebugItem(offsetWriter, debugWriter, |
| classSection.getParameterNames(methodKey), debugItems); |
| int codeItemOffset; |
| try { |
| codeItemOffset = writeCodeItem( |
| codeWriter, ehBuf, methodKey, tryBlocks, instructions, debugItemOffset); |
| } catch (RuntimeException ex) { |
| throw new ExceptionWithContext(ex, "Exception occurred while writing code_item for method %s", |
| methodSection.getMethodReference(methodKey)); |
| } |
| |
| if (codeItemOffset != -1) { |
| codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset)); |
| } |
| } |
| } |
| |
| offsetWriter.align(); |
| codeSectionOffset = offsetWriter.getPosition(); |
| |
| codeWriter.close(); |
| temp.writeTo(offsetWriter); |
| temp.close(); |
| |
| for (CodeItemOffset<MethodKey> codeOffset: codeOffsets) { |
| classSection.setCodeItemOffset(codeOffset.method, codeSectionOffset + codeOffset.codeOffset); |
| } |
| } |
| |
| private void fixInstructions(@Nonnull MutableMethodImplementation methodImplementation) { |
| List<? extends Instruction> instructions = methodImplementation.getInstructions(); |
| |
| for (int i=0; i<instructions.size(); i++) { |
| Instruction instruction = instructions.get(i); |
| |
| if (instruction.getOpcode() == Opcode.CONST_STRING) { |
| if (stringSection.getItemIndex( |
| (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) { |
| methodImplementation.replaceInstruction(i, new BuilderInstruction31c(Opcode.CONST_STRING_JUMBO, |
| ((OneRegisterInstruction)instruction).getRegisterA(), |
| ((ReferenceInstruction)instruction).getReference())); |
| } |
| } |
| } |
| } |
| |
| private int writeDebugItem(@Nonnull DexDataWriter writer, |
| @Nonnull DebugWriter<StringKey, TypeKey> debugWriter, |
| @Nullable Iterable<? extends StringKey> parameterNames, |
| @Nullable Iterable<? extends DebugItem> debugItems) throws IOException { |
| int parameterCount = 0; |
| int lastNamedParameterIndex = -1; |
| if (parameterNames != null) { |
| parameterCount = Iterables.size(parameterNames); |
| int index = 0; |
| for (StringKey parameterName: parameterNames) { |
| if (parameterName != null) { |
| lastNamedParameterIndex = index; |
| } |
| index++; |
| } |
| } |
| |
| |
| if (lastNamedParameterIndex == -1 && (debugItems == null || Iterables.isEmpty(debugItems))) { |
| return NO_OFFSET; |
| } |
| |
| numDebugInfoItems++; |
| |
| int debugItemOffset = writer.getPosition(); |
| int startingLineNumber = 0; |
| |
| if (debugItems != null) { |
| for (org.jf.dexlib2.iface.debug.DebugItem debugItem: debugItems) { |
| if (debugItem instanceof LineNumber) { |
| startingLineNumber = ((LineNumber)debugItem).getLineNumber(); |
| break; |
| } |
| } |
| } |
| writer.writeUleb128(startingLineNumber); |
| |
| writer.writeUleb128(parameterCount); |
| if (parameterNames != null) { |
| int index = 0; |
| for (StringKey parameterName: parameterNames) { |
| if (index == parameterCount) { |
| break; |
| } |
| index++; |
| writer.writeUleb128(stringSection.getNullableItemIndex(parameterName) + 1); |
| } |
| } |
| |
| if (debugItems != null) { |
| debugWriter.reset(startingLineNumber); |
| |
| for (DebugItem debugItem: debugItems) { |
| classSection.writeDebugItem(debugWriter, debugItem); |
| } |
| } |
| // write an END_SEQUENCE opcode, to end the debug item |
| writer.write(0); |
| |
| return debugItemOffset; |
| } |
| |
| private int writeCodeItem(@Nonnull DexDataWriter writer, |
| @Nonnull ByteArrayOutputStream ehBuf, |
| @Nonnull MethodKey methodKey, |
| @Nonnull List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks, |
| @Nullable Iterable<? extends Instruction> instructions, |
| int debugItemOffset) throws IOException { |
| if (instructions == null && debugItemOffset == NO_OFFSET) { |
| return -1; |
| } |
| |
| numCodeItemItems++; |
| |
| writer.align(); |
| |
| int codeItemOffset = writer.getPosition(); |
| |
| writer.writeUshort(classSection.getRegisterCount(methodKey)); |
| |
| boolean isStatic = AccessFlags.STATIC.isSet(classSection.getMethodAccessFlags(methodKey)); |
| Collection<? extends TypeKey> parameters = typeListSection.getTypes( |
| protoSection.getParameters(methodSection.getPrototype(methodKey))); |
| |
| writer.writeUshort(MethodUtil.getParameterRegisterCount(parameters, isStatic)); |
| |
| if (instructions != null) { |
| tryBlocks = TryListBuilder.massageTryBlocks(tryBlocks); |
| |
| int outParamCount = 0; |
| int codeUnitCount = 0; |
| for (Instruction instruction: instructions) { |
| codeUnitCount += instruction.getCodeUnits(); |
| if (instruction.getOpcode().referenceType == ReferenceType.METHOD) { |
| ReferenceInstruction refInsn = (ReferenceInstruction)instruction; |
| MethodReference methodRef = (MethodReference)refInsn.getReference(); |
| Opcode opcode = instruction.getOpcode(); |
| int paramCount; |
| if (InstructionUtil.isInvokePolymorphic(opcode)) { |
| paramCount = ((VariableRegisterInstruction)instruction).getRegisterCount(); |
| } else { |
| paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(opcode)); |
| } |
| if (paramCount > outParamCount) { |
| outParamCount = paramCount; |
| } |
| } |
| } |
| |
| writer.writeUshort(outParamCount); |
| writer.writeUshort(tryBlocks.size()); |
| writer.writeInt(debugItemOffset); |
| |
| InstructionWriter instructionWriter = |
| InstructionWriter.makeInstructionWriter(opcodes, writer, stringSection, typeSection, fieldSection, |
| methodSection, protoSection); |
| |
| writer.writeInt(codeUnitCount); |
| int codeOffset = 0; |
| for (Instruction instruction: instructions) { |
| try { |
| switch (instruction.getOpcode().format) { |
| case Format10t: |
| instructionWriter.write((Instruction10t)instruction); |
| break; |
| case Format10x: |
| instructionWriter.write((Instruction10x)instruction); |
| break; |
| case Format11n: |
| instructionWriter.write((Instruction11n)instruction); |
| break; |
| case Format11x: |
| instructionWriter.write((Instruction11x)instruction); |
| break; |
| case Format12x: |
| instructionWriter.write((Instruction12x)instruction); |
| break; |
| case Format20bc: |
| instructionWriter.write((Instruction20bc)instruction); |
| break; |
| case Format20t: |
| instructionWriter.write((Instruction20t)instruction); |
| break; |
| case Format21c: |
| instructionWriter.write((Instruction21c)instruction); |
| break; |
| case Format21ih: |
| instructionWriter.write((Instruction21ih)instruction); |
| break; |
| case Format21lh: |
| instructionWriter.write((Instruction21lh)instruction); |
| break; |
| case Format21s: |
| instructionWriter.write((Instruction21s)instruction); |
| break; |
| case Format21t: |
| instructionWriter.write((Instruction21t)instruction); |
| break; |
| case Format22b: |
| instructionWriter.write((Instruction22b)instruction); |
| break; |
| case Format22c: |
| instructionWriter.write((Instruction22c)instruction); |
| break; |
| case Format22cs: |
| instructionWriter.write((Instruction22cs)instruction); |
| break; |
| case Format22s: |
| instructionWriter.write((Instruction22s)instruction); |
| break; |
| case Format22t: |
| instructionWriter.write((Instruction22t)instruction); |
| break; |
| case Format22x: |
| instructionWriter.write((Instruction22x)instruction); |
| break; |
| case Format23x: |
| instructionWriter.write((Instruction23x)instruction); |
| break; |
| case Format30t: |
| instructionWriter.write((Instruction30t)instruction); |
| break; |
| case Format31c: |
| instructionWriter.write((Instruction31c)instruction); |
| break; |
| case Format31i: |
| instructionWriter.write((Instruction31i)instruction); |
| break; |
| case Format31t: |
| instructionWriter.write((Instruction31t)instruction); |
| break; |
| case Format32x: |
| instructionWriter.write((Instruction32x)instruction); |
| break; |
| case Format35c: |
| instructionWriter.write((Instruction35c)instruction); |
| break; |
| case Format35mi: |
| instructionWriter.write((Instruction35mi)instruction); |
| break; |
| case Format35ms: |
| instructionWriter.write((Instruction35ms)instruction); |
| break; |
| case Format3rc: |
| instructionWriter.write((Instruction3rc)instruction); |
| break; |
| case Format3rmi: |
| instructionWriter.write((Instruction3rmi)instruction); |
| break; |
| case Format3rms: |
| instructionWriter.write((Instruction3rms)instruction); |
| break; |
| case Format45cc: |
| instructionWriter.write((Instruction45cc)instruction); |
| break; |
| case Format4rcc: |
| instructionWriter.write((Instruction4rcc)instruction); |
| break; |
| case Format51l: |
| instructionWriter.write((Instruction51l)instruction); |
| break; |
| case ArrayPayload: |
| instructionWriter.write((ArrayPayload)instruction); |
| break; |
| case PackedSwitchPayload: |
| instructionWriter.write((PackedSwitchPayload)instruction); |
| break; |
| case SparseSwitchPayload: |
| instructionWriter.write((SparseSwitchPayload)instruction); |
| break; |
| default: |
| throw new ExceptionWithContext("Unsupported instruction format: %s", |
| instruction.getOpcode().format); |
| } |
| } catch (RuntimeException ex) { |
| throw new ExceptionWithContext(ex, "Error while writing instruction at code offset 0x%x", codeOffset); |
| } |
| codeOffset += instruction.getCodeUnits(); |
| } |
| |
| if (tryBlocks.size() > 0) { |
| writer.align(); |
| |
| // filter out unique lists of exception handlers |
| Map<List<? extends ExceptionHandler>, Integer> exceptionHandlerOffsetMap = Maps.newHashMap(); |
| for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) { |
| exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0); |
| } |
| DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); |
| |
| for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) { |
| int startAddress = tryBlock.getStartCodeAddress(); |
| int endAddress = startAddress + tryBlock.getCodeUnitCount(); |
| |
| int tbCodeUnitCount = endAddress - startAddress; |
| |
| writer.writeInt(startAddress); |
| writer.writeUshort(tbCodeUnitCount); |
| |
| if (tryBlock.getExceptionHandlers().size() == 0) { |
| throw new ExceptionWithContext("No exception handlers for the try block!"); |
| } |
| |
| Integer offset = exceptionHandlerOffsetMap.get(tryBlock.getExceptionHandlers()); |
| if (offset != 0) { |
| // exception handler has already been written out, just use it |
| writer.writeUshort(offset); |
| } else { |
| // if offset has not been set yet, we are about to write out a new exception handler |
| offset = ehBuf.size(); |
| writer.writeUshort(offset); |
| exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), offset); |
| |
| // check if the last exception handler is a catch-all and adjust the size accordingly |
| int ehSize = tryBlock.getExceptionHandlers().size(); |
| ExceptionHandler ehLast = tryBlock.getExceptionHandlers().get(ehSize-1); |
| if (ehLast.getExceptionType() == null) { |
| ehSize = ehSize * (-1) + 1; |
| } |
| |
| // now let's layout the exception handlers, assuming that catch-all is always last |
| DexDataWriter.writeSleb128(ehBuf, ehSize); |
| for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) { |
| TypeKey exceptionTypeKey = classSection.getExceptionType(eh); |
| |
| int codeAddress = eh.getHandlerCodeAddress(); |
| |
| if (exceptionTypeKey != null) { |
| //regular exception handling |
| DexDataWriter.writeUleb128(ehBuf, typeSection.getItemIndex(exceptionTypeKey)); |
| DexDataWriter.writeUleb128(ehBuf, codeAddress); |
| } else { |
| //catch-all |
| DexDataWriter.writeUleb128(ehBuf, codeAddress); |
| } |
| } |
| } |
| } |
| |
| if (ehBuf.size() > 0) { |
| ehBuf.writeTo(writer); |
| ehBuf.reset(); |
| } |
| } |
| } else { |
| // no instructions, all we have is the debug item offset |
| writer.writeUshort(0); |
| writer.writeUshort(0); |
| writer.writeInt(debugItemOffset); |
| writer.writeInt(0); |
| } |
| |
| return codeItemOffset; |
| } |
| |
| private int calcNumItems() { |
| int numItems = 0; |
| |
| // header item |
| numItems++; |
| |
| if (stringSection.getItems().size() > 0) { |
| numItems += 2; // index and data |
| } |
| if (typeSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (protoSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (fieldSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (methodSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (typeListSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (numEncodedArrayItems > 0) { |
| numItems++; |
| } |
| if (annotationSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (annotationSetSection.getItems().size() > 0 || shouldCreateEmptyAnnotationSet()) { |
| numItems++; |
| } |
| if (numAnnotationSetRefItems > 0) { |
| numItems++; |
| } |
| if (numAnnotationDirectoryItems > 0) { |
| numItems++; |
| } |
| if (numDebugInfoItems > 0) { |
| numItems++; |
| } |
| if (numCodeItemItems > 0) { |
| numItems++; |
| } |
| if (classSection.getItems().size() > 0) { |
| numItems++; |
| } |
| if (numClassDataItems > 0) { |
| numItems++; |
| } |
| // map item itself |
| numItems++; |
| |
| return numItems; |
| } |
| |
| private void writeMapItem(@Nonnull DexDataWriter writer) throws IOException{ |
| writer.align(); |
| mapSectionOffset = writer.getPosition(); |
| int numItems = calcNumItems(); |
| |
| writer.writeInt(numItems); |
| |
| // index section |
| writeMapItem(writer, ItemType.HEADER_ITEM, 1, 0); |
| writeMapItem(writer, ItemType.STRING_ID_ITEM, stringSection.getItems().size(), stringIndexSectionOffset); |
| writeMapItem(writer, ItemType.TYPE_ID_ITEM, typeSection.getItems().size(), typeSectionOffset); |
| writeMapItem(writer, ItemType.PROTO_ID_ITEM, protoSection.getItems().size(), protoSectionOffset); |
| writeMapItem(writer, ItemType.FIELD_ID_ITEM, fieldSection.getItems().size(), fieldSectionOffset); |
| writeMapItem(writer, ItemType.METHOD_ID_ITEM, methodSection.getItems().size(), methodSectionOffset); |
| writeMapItem(writer, ItemType.CLASS_DEF_ITEM, classSection.getItems().size(), classIndexSectionOffset); |
| |
| // data section |
| writeMapItem(writer, ItemType.STRING_DATA_ITEM, stringSection.getItems().size(), stringDataSectionOffset); |
| writeMapItem(writer, ItemType.TYPE_LIST, typeListSection.getItems().size(), typeListSectionOffset); |
| writeMapItem(writer, ItemType.ENCODED_ARRAY_ITEM, numEncodedArrayItems, encodedArraySectionOffset); |
| writeMapItem(writer, ItemType.ANNOTATION_ITEM, annotationSection.getItems().size(), annotationSectionOffset); |
| writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM, |
| annotationSetSection.getItems().size() + (shouldCreateEmptyAnnotationSet() ? 1 : 0), annotationSetSectionOffset); |
| writeMapItem(writer, ItemType.ANNOTATION_SET_REF_LIST, numAnnotationSetRefItems, annotationSetRefSectionOffset); |
| writeMapItem(writer, ItemType.ANNOTATION_DIRECTORY_ITEM, numAnnotationDirectoryItems, |
| annotationDirectorySectionOffset); |
| writeMapItem(writer, ItemType.DEBUG_INFO_ITEM, numDebugInfoItems, debugSectionOffset); |
| writeMapItem(writer, ItemType.CODE_ITEM, numCodeItemItems, codeSectionOffset); |
| writeMapItem(writer, ItemType.CLASS_DATA_ITEM, numClassDataItems, classDataSectionOffset); |
| writeMapItem(writer, ItemType.MAP_LIST, 1, mapSectionOffset); |
| } |
| |
| private void writeMapItem(@Nonnull DexDataWriter writer, int type, int size, int offset) throws IOException { |
| if (size > 0) { |
| writer.writeUshort(type); |
| writer.writeUshort(0); |
| writer.writeInt(size); |
| writer.writeInt(offset); |
| } |
| } |
| |
| private void writeHeader(@Nonnull DexDataWriter writer, int dataOffset, int fileSize) throws IOException { |
| // Write the appropriate header. |
| writer.write(HeaderItem.getMagicForApi(opcodes.api)); |
| |
| // checksum placeholder |
| writer.writeInt(0); |
| |
| // signature placeholder |
| writer.write(new byte[20]); |
| |
| writer.writeInt(fileSize); |
| writer.writeInt(HeaderItem.ITEM_SIZE); |
| writer.writeInt(HeaderItem.LITTLE_ENDIAN_TAG); |
| |
| // link |
| writer.writeInt(0); |
| writer.writeInt(0); |
| |
| // map |
| writer.writeInt(mapSectionOffset); |
| |
| // index sections |
| |
| writeSectionInfo(writer, stringSection.getItems().size(), stringIndexSectionOffset); |
| writeSectionInfo(writer, typeSection.getItems().size(), typeSectionOffset); |
| writeSectionInfo(writer, protoSection.getItems().size(), protoSectionOffset); |
| writeSectionInfo(writer, fieldSection.getItems().size(), fieldSectionOffset); |
| writeSectionInfo(writer, methodSection.getItems().size(), methodSectionOffset); |
| writeSectionInfo(writer, classSection.getItems().size(), classIndexSectionOffset); |
| |
| // data section |
| writer.writeInt(fileSize - dataOffset); |
| writer.writeInt(dataOffset); |
| } |
| |
| private void writeSectionInfo(DexDataWriter writer, int numItems, int offset) throws IOException { |
| writer.writeInt(numItems); |
| if (numItems > 0) { |
| writer.writeInt(offset); |
| } else { |
| writer.writeInt(0); |
| } |
| } |
| |
| private boolean shouldCreateEmptyAnnotationSet() { |
| // Workaround for a crash in Dalvik VM before Jelly Bean MR1 (4.2) |
| // which is triggered by NO_OFFSET in parameter annotation list. |
| // (https://code.google.com/p/android/issues/detail?id=35304) |
| return (opcodes.api < 17); |
| } |
| |
| public abstract class SectionProvider { |
| @Nonnull public abstract StringSectionType getStringSection(); |
| @Nonnull public abstract TypeSectionType getTypeSection(); |
| @Nonnull public abstract ProtoSectionType getProtoSection(); |
| @Nonnull public abstract FieldSectionType getFieldSection(); |
| @Nonnull public abstract MethodSectionType getMethodSection(); |
| @Nonnull public abstract ClassSectionType getClassSection(); |
| @Nonnull public abstract TypeListSectionType getTypeListSection(); |
| @Nonnull public abstract AnnotationSectionType getAnnotationSection(); |
| @Nonnull public abstract AnnotationSetSectionType getAnnotationSetSection(); |
| } |
| } |