/* * Copyright (C) 2011 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.taobao.atlas.dexmerge.dx.merge; import com.taobao.atlas.dex.*; import com.taobao.atlas.dex.util.ByteOutput; import com.taobao.atlas.dexmerge.dx.util.ByteArrayAnnotatedOutput; import java.util.HashMap; import static com.taobao.atlas.dex.EncodedValueReader.*; /** * Maps the index offsets from one dex file to those in another. For example, if * you have string #5 in the old dex file, its position in the new dex file is * {@code strings[5]}. */ public final class IndexMap { private final Dex target; public final int[] stringIds; public final short[] typeIds; public final short[] protoIds; public final short[] fieldIds; public final short[] methodIds; private final HashMap<Integer, Integer> typeListOffsets; private final HashMap<Integer, Integer> annotationOffsets; private final HashMap<Integer, Integer> annotationSetOffsets; private final HashMap<Integer, Integer> annotationSetRefListOffsets; private final HashMap<Integer, Integer> annotationDirectoryOffsets; private final HashMap<Integer, Integer> staticValuesOffsets; public IndexMap(Dex target, TableOfContents tableOfContents) { this.target = target; this.stringIds = new int[tableOfContents.stringIds.size]; this.typeIds = new short[tableOfContents.typeIds.size]; this.protoIds = new short[tableOfContents.protoIds.size]; this.fieldIds = new short[tableOfContents.fieldIds.size]; this.methodIds = new short[tableOfContents.methodIds.size]; this.typeListOffsets = new HashMap<Integer, Integer>(); this.annotationOffsets = new HashMap<Integer, Integer>(); this.annotationSetOffsets = new HashMap<Integer, Integer>(); this.annotationSetRefListOffsets = new HashMap<Integer, Integer>(); this.annotationDirectoryOffsets = new HashMap<Integer, Integer>(); this.staticValuesOffsets = new HashMap<Integer, Integer>(); /* * A type list, annotation set, annotation directory, or static value at * offset 0 is always empty. Always map offset 0 to 0. */ this.typeListOffsets.put(0, 0); this.annotationSetOffsets.put(0, 0); this.annotationDirectoryOffsets.put(0, 0); this.staticValuesOffsets.put(0, 0); } public void putTypeListOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } typeListOffsets.put(oldOffset, newOffset); } public void putAnnotationOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } annotationOffsets.put(oldOffset, newOffset); } public void putAnnotationSetOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } annotationSetOffsets.put(oldOffset, newOffset); } public void putAnnotationSetRefListOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } annotationSetRefListOffsets.put(oldOffset, newOffset); } public void putAnnotationDirectoryOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } annotationDirectoryOffsets.put(oldOffset, newOffset); } public void putStaticValuesOffset(int oldOffset, int newOffset) { if (oldOffset <= 0 || newOffset <= 0) { throw new IllegalArgumentException(); } staticValuesOffsets.put(oldOffset, newOffset); } public int adjustString(int stringIndex) { return stringIndex == ClassDef.NO_INDEX ? ClassDef.NO_INDEX : stringIds[stringIndex]; } public int adjustType(int typeIndex) { return (typeIndex == ClassDef.NO_INDEX) ? ClassDef.NO_INDEX : (typeIds[typeIndex] & 0xffff); } public TypeList adjustTypeList(TypeList typeList) { if (typeList == TypeList.EMPTY) { return typeList; } short[] types = typeList.getTypes().clone(); for (int i = 0; i < types.length; i++) { types[i] = (short) adjustType(types[i]); } return new TypeList(target, types); } public int adjustProto(int protoIndex) { return protoIds[protoIndex] & 0xffff; } public int adjustField(int fieldIndex) { return fieldIds[fieldIndex] & 0xffff; } public int adjustMethod(int methodIndex) { return methodIds[methodIndex] & 0xffff; } public int adjustTypeListOffset(int typeListOffset) { return typeListOffsets.get(typeListOffset); } public int adjustAnnotation(int annotationOffset) { return annotationOffsets.get(annotationOffset); } public int adjustAnnotationSet(int annotationSetOffset) { return annotationSetOffsets.get(annotationSetOffset); } public int adjustAnnotationSetRefList(int annotationSetRefListOffset) { return annotationSetRefListOffsets.get(annotationSetRefListOffset); } public int adjustAnnotationDirectory(int annotationDirectoryOffset) { return annotationDirectoryOffsets.get(annotationDirectoryOffset); } public int adjustStaticValues(int staticValuesOffset) { return staticValuesOffsets.get(staticValuesOffset); } public MethodId adjust(MethodId methodId) { return new MethodId(target, adjustType(methodId.getDeclaringClassIndex()), adjustProto(methodId.getProtoIndex()), adjustString(methodId.getNameIndex())); } public FieldId adjust(FieldId fieldId) { return new FieldId(target, adjustType(fieldId.getDeclaringClassIndex()), adjustType(fieldId.getTypeIndex()), adjustString(fieldId.getNameIndex())); } public ProtoId adjust(ProtoId protoId) { return new ProtoId(target, adjustString(protoId.getShortyIndex()), adjustType(protoId.getReturnTypeIndex()), adjustTypeListOffset(protoId.getParametersOffset())); } public ClassDef adjust(ClassDef classDef) { return new ClassDef(target, classDef.getOffset(), adjustType(classDef.getTypeIndex()), classDef.getAccessFlags(), adjustType(classDef.getSupertypeIndex()), adjustTypeListOffset(classDef.getInterfacesOffset()), classDef.getSourceFileIndex(), classDef.getAnnotationsOffset(), classDef.getClassDataOffset(), classDef.getStaticValuesOffset()); } public SortableType adjust(SortableType sortableType) { return new SortableType(sortableType.getDex(), sortableType.getIndexMap(), adjust(sortableType.getClassDef())); } public EncodedValue adjustEncodedValue(EncodedValue encodedValue) { ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); new EncodedValueTransformer(out).transform(new EncodedValueReader(encodedValue)); return new EncodedValue(out.toByteArray()); } public EncodedValue adjustEncodedArray(EncodedValue encodedArray) { ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); new EncodedValueTransformer(out).transformArray( new EncodedValueReader(encodedArray, ENCODED_ARRAY)); return new EncodedValue(out.toByteArray()); } public Annotation adjust(Annotation annotation) { ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); new EncodedValueTransformer(out).transformAnnotation( annotation.getReader()); return new Annotation(target, annotation.getVisibility(), new EncodedValue(out.toByteArray())); } /** * Adjust an encoded value or array. */ private final class EncodedValueTransformer { private final ByteOutput out; public EncodedValueTransformer(ByteOutput out) { this.out = out; } public void transform(EncodedValueReader reader) { // TODO: extract this into a helper class, EncodedValueWriter switch (reader.peek()) { case ENCODED_BYTE: EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_BYTE, reader.readByte()); break; case ENCODED_SHORT: EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_SHORT, reader.readShort()); break; case ENCODED_INT: EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_INT, reader.readInt()); break; case ENCODED_LONG: EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_LONG, reader.readLong()); break; case ENCODED_CHAR: EncodedValueCodec.writeUnsignedIntegralValue(out, ENCODED_CHAR, reader.readChar()); break; case ENCODED_FLOAT: // Shift value left 32 so that right-zero-extension works. long longBits = ((long) Float.floatToIntBits(reader.readFloat())) << 32; EncodedValueCodec.writeRightZeroExtendedValue(out, ENCODED_FLOAT, longBits); break; case ENCODED_DOUBLE: EncodedValueCodec.writeRightZeroExtendedValue( out, ENCODED_DOUBLE, Double.doubleToLongBits(reader.readDouble())); break; case ENCODED_STRING: EncodedValueCodec.writeUnsignedIntegralValue( out, ENCODED_STRING, adjustString(reader.readString())); break; case ENCODED_TYPE: EncodedValueCodec.writeUnsignedIntegralValue( out, ENCODED_TYPE, adjustType(reader.readType())); break; case ENCODED_FIELD: EncodedValueCodec.writeUnsignedIntegralValue( out, ENCODED_FIELD, adjustField(reader.readField())); break; case ENCODED_ENUM: EncodedValueCodec.writeUnsignedIntegralValue( out, ENCODED_ENUM, adjustField(reader.readEnum())); break; case ENCODED_METHOD: EncodedValueCodec.writeUnsignedIntegralValue( out, ENCODED_METHOD, adjustMethod(reader.readMethod())); break; case ENCODED_ARRAY: writeTypeAndArg(ENCODED_ARRAY, 0); transformArray(reader); break; case ENCODED_ANNOTATION: writeTypeAndArg(ENCODED_ANNOTATION, 0); transformAnnotation(reader); break; case ENCODED_NULL: reader.readNull(); writeTypeAndArg(ENCODED_NULL, 0); break; case ENCODED_BOOLEAN: boolean value = reader.readBoolean(); writeTypeAndArg(ENCODED_BOOLEAN, value ? 1 : 0); break; default: throw new DexException2("Unexpected type: " + Integer.toHexString(reader.peek())); } } private void transformAnnotation(EncodedValueReader reader) { int fieldCount = reader.readAnnotation(); Leb128.writeUnsignedLeb128(out, adjustType(reader.getAnnotationType())); Leb128.writeUnsignedLeb128(out, fieldCount); for (int i = 0; i < fieldCount; i++) { Leb128.writeUnsignedLeb128(out, adjustString(reader.readAnnotationName())); transform(reader); } } private void transformArray(EncodedValueReader reader) { int size = reader.readArray(); Leb128.writeUnsignedLeb128(out, size); for (int i = 0; i < size; i++) { transform(reader); } } private void writeTypeAndArg(int type, int arg) { out.writeByte((arg << 5) | type); } } }