blob: f47c03ccbf638349bdfdd56b4be63796ef1b7a32 [file] [log] [blame]
/*
* 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.android.dx.merge;
import com.android.dex.Annotation;
import com.android.dex.util.ByteOutput;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import com.android.dex.DexException;
import com.android.dex.EncodedValue;
import com.android.dex.EncodedValueReader;
import static com.android.dex.EncodedValueReader.ENCODED_ANNOTATION;
import static com.android.dex.EncodedValueReader.ENCODED_ARRAY;
import static com.android.dex.EncodedValueReader.ENCODED_BOOLEAN;
import static com.android.dex.EncodedValueReader.ENCODED_BYTE;
import static com.android.dex.EncodedValueReader.ENCODED_CHAR;
import static com.android.dex.EncodedValueReader.ENCODED_DOUBLE;
import static com.android.dex.EncodedValueReader.ENCODED_ENUM;
import static com.android.dex.EncodedValueReader.ENCODED_FIELD;
import static com.android.dex.EncodedValueReader.ENCODED_FLOAT;
import static com.android.dex.EncodedValueReader.ENCODED_INT;
import static com.android.dex.EncodedValueReader.ENCODED_LONG;
import static com.android.dex.EncodedValueReader.ENCODED_METHOD;
import static com.android.dex.EncodedValueReader.ENCODED_NULL;
import static com.android.dex.EncodedValueReader.ENCODED_SHORT;
import static com.android.dex.EncodedValueReader.ENCODED_STRING;
import static com.android.dex.EncodedValueReader.ENCODED_TYPE;
import com.android.dex.EncodedValueCodec;
import com.android.dex.FieldId;
import com.android.dex.Leb128;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import com.android.dex.TableOfContents;
import com.android.dex.TypeList;
import com.android.dx.util.ByteArrayAnnotatedOutput;
import java.util.HashMap;
/**
* 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 DexException("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);
}
}
}