blob: c506fa6250d8c938549d582c6779b401b4a5caeb [file] [log] [blame]
/*
* Copyright (C) 2014 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 dexfuzz.program;
import dexfuzz.Log;
import dexfuzz.rawdex.FieldIdItem;
import dexfuzz.rawdex.MethodIdItem;
import dexfuzz.rawdex.Offset;
import dexfuzz.rawdex.Offsettable;
import dexfuzz.rawdex.ProtoIdItem;
import dexfuzz.rawdex.RawDexFile;
import dexfuzz.rawdex.RawDexObject.IndexUpdateKind;
import dexfuzz.rawdex.StringDataItem;
import dexfuzz.rawdex.StringIdItem;
import dexfuzz.rawdex.TypeIdItem;
import dexfuzz.rawdex.TypeItem;
import dexfuzz.rawdex.TypeList;
import java.util.ArrayList;
import java.util.List;
/**
* Responsible for the finding and creation of TypeIds, MethodIds, FieldIds, and StringIds,
* during mutation.
*/
public class IdCreator {
private RawDexFile rawDexFile;
public IdCreator(RawDexFile rawDexFile) {
this.rawDexFile = rawDexFile;
}
private int findProtoIdInsertionPoint(String signature) {
int returnTypeIdx = findTypeId(convertSignatureToReturnType(signature));
String[] parameterListStrings = convertSignatureToParameterList(signature);
TypeList parameterList = null;
if (parameterListStrings.length > 0) {
parameterList = findTypeList(parameterListStrings);
}
if (returnTypeIdx < 0) {
Log.errorAndQuit("Did not create necessary return type before finding insertion "
+ "point for new proto!");
}
if (parameterListStrings.length > 0 && parameterList == null) {
Log.errorAndQuit("Did not create necessary parameter list before finding insertion "
+ "point for new proto!");
}
int protoIdIdx = 0;
for (ProtoIdItem protoId : rawDexFile.protoIds) {
if (returnTypeIdx < protoId.returnTypeIdx) {
break;
}
if (returnTypeIdx == protoId.returnTypeIdx
&& parameterListStrings.length == 0) {
break;
}
if (returnTypeIdx == protoId.returnTypeIdx
&& parameterListStrings.length > 0
&& protoId.parametersOff.pointsToSomething()
&& parameterList.comesBefore(
(TypeList) protoId.parametersOff.getPointedToItem())) {
break;
}
protoIdIdx++;
}
return protoIdIdx;
}
private int findMethodIdInsertionPoint(String className, String methodName, String signature) {
int classIdx = findTypeId(className);
int nameIdx = findString(methodName);
int protoIdx = findProtoId(signature);
if (classIdx < 0 || nameIdx < 0 || protoIdx < 0) {
Log.errorAndQuit("Did not create necessary class, name or proto strings before finding "
+ " insertion point for new method!");
}
int methodIdIdx = 0;
for (MethodIdItem methodId : rawDexFile.methodIds) {
if (classIdx < methodId.classIdx) {
break;
}
if (classIdx == methodId.classIdx && nameIdx < methodId.nameIdx) {
break;
}
if (classIdx == methodId.classIdx && nameIdx == methodId.nameIdx
&& protoIdx < methodId.protoIdx) {
break;
}
methodIdIdx++;
}
return methodIdIdx;
}
private int findTypeIdInsertionPoint(String className) {
int descriptorIdx = findString(className);
if (descriptorIdx < 0) {
Log.errorAndQuit("Did not create necessary descriptor string before finding "
+ " insertion point for new type!");
}
int typeIdIdx = 0;
for (TypeIdItem typeId : rawDexFile.typeIds) {
if (descriptorIdx < typeId.descriptorIdx) {
break;
}
typeIdIdx++;
}
return typeIdIdx;
}
private int findStringDataInsertionPoint(String string) {
int stringDataIdx = 0;
for (StringDataItem stringData : rawDexFile.stringDatas) {
if (stringData.getSize() > 0 && stringData.getString().compareTo(string) >= 0) {
break;
}
stringDataIdx++;
}
return stringDataIdx;
}
private int findFieldIdInsertionPoint(String className, String typeName, String fieldName) {
int classIdx = findTypeId(className);
int typeIdx = findTypeId(typeName);
int nameIdx = findString(fieldName);
if (classIdx < 0 || typeIdx < 0 || nameIdx < 0) {
Log.errorAndQuit("Did not create necessary class, type or name strings before finding "
+ " insertion point for new field!");
}
int fieldIdIdx = 0;
for (FieldIdItem fieldId : rawDexFile.fieldIds) {
if (classIdx < fieldId.classIdx) {
break;
}
if (classIdx == fieldId.classIdx && nameIdx < fieldId.nameIdx) {
break;
}
if (classIdx == fieldId.classIdx && nameIdx == fieldId.nameIdx
&& typeIdx < fieldId.typeIdx) {
break;
}
fieldIdIdx++;
}
return fieldIdIdx;
}
private int createMethodId(String className, String methodName, String signature) {
if (rawDexFile.methodIds.size() >= 65536) {
Log.errorAndQuit("Referenced too many methods for the DEX file.");
}
// Search for (or create) the prototype.
int protoIdx = findOrCreateProtoId(signature);
// Search for (or create) the owning class.
// NB: findOrCreateProtoId could create new types, so this must come
// after it!
int typeIdIdx = findOrCreateTypeId(className);
// Search for (or create) the string representing the method name.
// NB: findOrCreateProtoId/TypeId could create new strings, so this must come
// after them!
int methodNameStringIdx = findOrCreateString(methodName);
// Create MethodIdItem.
MethodIdItem newMethodId = new MethodIdItem();
newMethodId.classIdx = (short) typeIdIdx;
newMethodId.protoIdx = (short) protoIdx;
newMethodId.nameIdx = methodNameStringIdx;
// MethodIds must be ordered.
int newMethodIdIdx = findMethodIdInsertionPoint(className, methodName, signature);
rawDexFile.methodIds.add(newMethodIdIdx, newMethodId);
// Insert into OffsetTracker.
if (newMethodIdIdx == 0) {
rawDexFile.getOffsetTracker()
.insertNewOffsettableAsFirstOfType(newMethodId, rawDexFile);
} else {
MethodIdItem prevMethodId = rawDexFile.methodIds.get(newMethodIdIdx - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newMethodId, prevMethodId);
}
Log.info(String.format("Created new MethodIdItem for %s %s %s, index: 0x%04x",
className, methodName, signature, newMethodIdIdx));
// Now that we've potentially moved a lot of method IDs along, all references
// to them need to be updated.
rawDexFile.incrementIndex(IndexUpdateKind.METHOD_ID, newMethodIdIdx);
// All done, return the index for the new method.
return newMethodIdIdx;
}
private int findMethodId(String className, String methodName, String signature) {
int classIdx = findTypeId(className);
if (classIdx == -1) {
return -1;
}
int nameIdx = findString(methodName);
if (nameIdx == -1) {
return -1;
}
int protoIdx = findProtoId(signature);
if (nameIdx == -1) {
return -1;
}
int methodIdIdx = 0;
for (MethodIdItem methodId : rawDexFile.methodIds) {
if (classIdx == methodId.classIdx
&& nameIdx == methodId.nameIdx
&& protoIdx == methodId.protoIdx) {
return methodIdIdx;
}
methodIdIdx++;
}
return -1;
}
/**
* Given a fully qualified class name (Ljava/lang/System;), method name (gc) and
* and signature (()V), either find the MethodId in our DEX file's table, or create it.
*/
public int findOrCreateMethodId(String className, String methodName, String shorty) {
int methodIdIdx = findMethodId(className, methodName, shorty);
if (methodIdIdx != -1) {
return methodIdIdx;
}
return createMethodId(className, methodName, shorty);
}
private int createTypeId(String className) {
if (rawDexFile.typeIds.size() >= 65536) {
Log.errorAndQuit("Referenced too many classes for the DEX file.");
}
// Search for (or create) the string representing the class descriptor.
int descriptorStringIdx = findOrCreateString(className);
// Create TypeIdItem.
TypeIdItem newTypeId = new TypeIdItem();
newTypeId.descriptorIdx = descriptorStringIdx;
// TypeIds must be ordered.
int newTypeIdIdx = findTypeIdInsertionPoint(className);
rawDexFile.typeIds.add(newTypeIdIdx, newTypeId);
// Insert into OffsetTracker.
if (newTypeIdIdx == 0) {
rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newTypeId, rawDexFile);
} else {
TypeIdItem prevTypeId = rawDexFile.typeIds.get(newTypeIdIdx - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newTypeId, prevTypeId);
}
Log.info(String.format("Created new ClassIdItem for %s, index: 0x%04x",
className, newTypeIdIdx));
// Now that we've potentially moved a lot of type IDs along, all references
// to them need to be updated.
rawDexFile.incrementIndex(IndexUpdateKind.TYPE_ID, newTypeIdIdx);
// All done, return the index for the new class.
return newTypeIdIdx;
}
private int findTypeId(String className) {
int descriptorIdx = findString(className);
if (descriptorIdx == -1) {
return -1;
}
// Search for class.
int typeIdIdx = 0;
for (TypeIdItem typeId : rawDexFile.typeIds) {
if (descriptorIdx == typeId.descriptorIdx) {
return typeIdIdx;
}
typeIdIdx++;
}
return -1;
}
/**
* Given a fully qualified class name (Ljava/lang/System;)
* either find the TypeId in our DEX file's table, or create it.
*/
public int findOrCreateTypeId(String className) {
int typeIdIdx = findTypeId(className);
if (typeIdIdx != -1) {
return typeIdIdx;
}
return createTypeId(className);
}
private int createString(String string) {
// Didn't find it, create one...
int stringsCount = rawDexFile.stringIds.size();
if (stringsCount != rawDexFile.stringDatas.size()) {
Log.errorAndQuit("Corrupted DEX file, len(StringIDs) != len(StringDatas)");
}
// StringData must be ordered.
int newStringIdx = findStringDataInsertionPoint(string);
// Create StringDataItem.
StringDataItem newStringData = new StringDataItem();
newStringData.setSize(string.length());
newStringData.setString(string);
rawDexFile.stringDatas.add(newStringIdx, newStringData);
// Insert into OffsetTracker.
// (Need to save the Offsettable, because the StringIdItem will point to it.)
Offsettable offsettableStringData = null;
if (newStringIdx == 0) {
offsettableStringData =
rawDexFile.getOffsetTracker()
.insertNewOffsettableAsFirstOfType(newStringData, rawDexFile);
} else {
StringDataItem prevStringData = rawDexFile.stringDatas.get(newStringIdx - 1);
offsettableStringData = rawDexFile.getOffsetTracker()
.insertNewOffsettableAfter(newStringData, prevStringData);
}
// Create StringIdItem.
StringIdItem newStringId = new StringIdItem();
newStringId.stringDataOff = new Offset(false);
newStringId.stringDataOff.pointToNew(offsettableStringData);
rawDexFile.stringIds.add(newStringIdx, newStringId);
// Insert into OffsetTracker.
if (newStringIdx == 0) {
rawDexFile.getOffsetTracker()
.insertNewOffsettableAsFirstOfType(newStringId, rawDexFile);
} else {
StringIdItem prevStringId = rawDexFile.stringIds.get(newStringIdx - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newStringId, prevStringId);
}
Log.info(String.format("Created new StringIdItem and StringDataItem for %s, index: 0x%04x",
string, newStringIdx));
// Now that we've potentially moved a lot of string IDs along, all references
// to them need to be updated.
rawDexFile.incrementIndex(IndexUpdateKind.STRING_ID, newStringIdx);
// All done, return the index for the new string.
return newStringIdx;
}
private int findString(String string) {
// Search for string.
int stringIdx = 0;
for (StringDataItem stringDataItem : rawDexFile.stringDatas) {
if (stringDataItem.getSize() == 0 && string.isEmpty()) {
return stringIdx;
} else if (stringDataItem.getSize() > 0 && stringDataItem.getString().equals(string)) {
return stringIdx;
}
stringIdx++;
}
return -1;
}
/**
* Given a string, either find the StringId in our DEX file's table, or create it.
*/
public int findOrCreateString(String string) {
int stringIdx = findString(string);
if (stringIdx != -1) {
return stringIdx;
}
return createString(string);
}
private int createFieldId(String className, String typeName, String fieldName) {
if (rawDexFile.fieldIds.size() >= 65536) {
Log.errorAndQuit("Referenced too many fields for the DEX file.");
}
// Search for (or create) the owning class.
int classIdx = findOrCreateTypeId(className);
// Search for (or create) the field's type.
int typeIdx = findOrCreateTypeId(typeName);
// The creation of the typeIdx may have changed the classIdx, search again!
classIdx = findOrCreateTypeId(className);
// Search for (or create) the string representing the field name.
int fieldNameStringIdx = findOrCreateString(fieldName);
// Create FieldIdItem.
FieldIdItem newFieldId = new FieldIdItem();
newFieldId.classIdx = (short) classIdx;
newFieldId.typeIdx = (short) typeIdx;
newFieldId.nameIdx = fieldNameStringIdx;
// FieldIds must be ordered.
int newFieldIdIdx = findFieldIdInsertionPoint(className, typeName, fieldName);
rawDexFile.fieldIds.add(newFieldIdIdx, newFieldId);
// Insert into OffsetTracker.
if (newFieldIdIdx == 0 && rawDexFile.fieldIds.size() == 1) {
// Special case: we didn't have any fields before!
rawDexFile.getOffsetTracker()
.insertNewOffsettableAsFirstEverField(newFieldId, rawDexFile);
} else if (newFieldIdIdx == 0) {
rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newFieldId, rawDexFile);
} else {
FieldIdItem prevFieldId = rawDexFile.fieldIds.get(newFieldIdIdx - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newFieldId, prevFieldId);
}
Log.info(String.format("Created new FieldIdItem for %s %s %s, index: 0x%04x",
className, typeName, fieldName, newFieldIdIdx));
// Now that we've potentially moved a lot of field IDs along, all references
// to them need to be updated.
rawDexFile.incrementIndex(IndexUpdateKind.FIELD_ID, newFieldIdIdx);
// All done, return the index for the new field.
return newFieldIdIdx;
}
private int findFieldId(String className, String typeName, String fieldName) {
int classIdx = findTypeId(className);
if (classIdx == -1) {
return -1;
}
int typeIdx = findTypeId(typeName);
if (typeIdx == -1) {
return -1;
}
int nameIdx = findString(fieldName);
if (nameIdx == -1) {
return -1;
}
int fieldIdIdx = 0;
for (FieldIdItem fieldId : rawDexFile.fieldIds) {
if (classIdx == fieldId.classIdx
&& typeIdx == fieldId.typeIdx
&& nameIdx == fieldId.nameIdx) {
return fieldIdIdx;
}
fieldIdIdx++;
}
return -1;
}
/**
* Given a field's fully qualified class name, type name, and name,
* either find the FieldId in our DEX file's table, or create it.
*/
public int findOrCreateFieldId(String className, String typeName, String fieldName) {
int fieldIdx = findFieldId(className, typeName, fieldName);
if (fieldIdx != -1) {
return fieldIdx;
}
return createFieldId(className, typeName, fieldName);
}
/**
* Returns a 1 or 2 element String[]. If 1 element, the only element is the return type
* part of the signature. If 2 elements, the first is the parameters, the second is
* the return type.
*/
private String[] convertSignatureToParametersAndReturnType(String signature) {
if (signature.charAt(0) != '(' || !signature.contains(")")) {
Log.errorAndQuit("Invalid signature: " + signature);
}
String[] elems = signature.substring(1).split("\\)");
return elems;
}
private String[] convertSignatureToParameterList(String signature) {
String[] elems = convertSignatureToParametersAndReturnType(signature);
String parameters = "";
if (elems.length == 2) {
parameters = elems[0];
}
List<String> parameterList = new ArrayList<String>();
int typePointer = 0;
while (typePointer != parameters.length()) {
if (elems[0].charAt(typePointer) == 'L') {
int start = typePointer;
// Read up to the next ;
while (elems[0].charAt(typePointer) != ';') {
typePointer++;
}
parameterList.add(parameters.substring(start, typePointer + 1));
} else {
parameterList.add(Character.toString(parameters.charAt(typePointer)));
}
typePointer++;
}
return parameterList.toArray(new String[]{});
}
private String convertSignatureToReturnType(String signature) {
String[] elems = convertSignatureToParametersAndReturnType(signature);
String returnType = "";
if (elems.length == 1) {
returnType = elems[0];
} else {
returnType = elems[1];
}
return returnType;
}
private String convertSignatureToShorty(String signature) {
String[] elems = convertSignatureToParametersAndReturnType(signature);
StringBuilder shortyBuilder = new StringBuilder();
String parameters = "";
String returnType = "";
if (elems.length == 1) {
shortyBuilder.append("V");
} else {
parameters = elems[0];
returnType = elems[1];
char returnChar = returnType.charAt(0);
// Arrays are references in shorties.
if (returnChar == '[') {
returnChar = 'L';
}
shortyBuilder.append(returnChar);
}
int typePointer = 0;
while (typePointer != parameters.length()) {
if (parameters.charAt(typePointer) == 'L') {
shortyBuilder.append('L');
// Read up to the next ;
while (parameters.charAt(typePointer) != ';') {
typePointer++;
if (typePointer == parameters.length()) {
Log.errorAndQuit("Illegal type specified in signature - L with no ;!");
}
}
} else if (parameters.charAt(typePointer) == '[') {
// Arrays are references in shorties.
shortyBuilder.append('L');
// Read past all the [s
while (parameters.charAt(typePointer) == '[') {
typePointer++;
}
if (parameters.charAt(typePointer) == 'L') {
// Read up to the next ;
while (parameters.charAt(typePointer) != ';') {
typePointer++;
if (typePointer == parameters.length()) {
Log.errorAndQuit("Illegal type specified in signature - L with no ;!");
}
}
}
} else {
shortyBuilder.append(parameters.charAt(typePointer));
}
typePointer++;
}
return shortyBuilder.toString();
}
private Integer[] convertParameterListToTypeIdList(String[] parameterList) {
List<Integer> typeIdList = new ArrayList<Integer>();
for (String parameter : parameterList) {
int typeIdx = findTypeId(parameter);
if (typeIdx == -1) {
return null;
}
typeIdList.add(typeIdx);
}
return typeIdList.toArray(new Integer[]{});
}
private TypeList createTypeList(String[] parameterList) {
TypeList typeList = new TypeList();
List<TypeItem> typeItemList = new ArrayList<TypeItem>();
// This must be done as two passes, one to create all the types,
// and then one to put them in the type list.
for (String parameter : parameterList) {
findOrCreateTypeId(parameter);
}
// Now actually put them in the list.
for (String parameter : parameterList) {
TypeItem typeItem = new TypeItem();
typeItem.typeIdx = (short) findOrCreateTypeId(parameter);
typeItemList.add(typeItem);
}
typeList.list = typeItemList.toArray(new TypeItem[]{});
typeList.size = typeItemList.size();
// Insert into OffsetTracker.
if (rawDexFile.typeLists == null) {
// Special case: we didn't have any fields before!
Log.info("Need to create first type list.");
rawDexFile.typeLists = new ArrayList<TypeList>();
rawDexFile.getOffsetTracker()
.insertNewOffsettableAsFirstEverTypeList(typeList, rawDexFile);
} else {
TypeList prevTypeList =
rawDexFile.typeLists.get(rawDexFile.typeLists.size() - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(typeList, prevTypeList);
}
// Finally, add this new TypeList to the list of them.
rawDexFile.typeLists.add(typeList);
return typeList;
}
private TypeList findTypeList(String[] parameterList) {
Integer[] typeIdList = convertParameterListToTypeIdList(parameterList);
if (typeIdList == null) {
return null;
}
if (rawDexFile.typeLists == null) {
// There's no type lists yet!
return null;
}
for (TypeList typeList : rawDexFile.typeLists) {
if (typeList.size != typeIdList.length) {
continue;
}
boolean found = true;
int idx = 0;
for (TypeItem typeItem : typeList.list) {
if (typeItem.typeIdx != typeIdList[idx]) {
found = false;
break;
}
idx++;
}
if (found && idx == parameterList.length) {
return typeList;
}
}
return null;
}
private TypeList findOrCreateTypeList(String[] parameterList) {
TypeList typeList = findTypeList(parameterList);
if (typeList != null) {
return typeList;
}
return createTypeList(parameterList);
}
private int createProtoId(String signature) {
String shorty = convertSignatureToShorty(signature);
String returnType = convertSignatureToReturnType(signature);
String[] parameterList = convertSignatureToParameterList(signature);
if (rawDexFile.protoIds.size() >= 65536) {
Log.errorAndQuit("Referenced too many protos for the DEX file.");
}
TypeList typeList = null;
Offsettable typeListOffsettable = null;
if (parameterList.length > 0) {
// Search for (or create) the parameter list.
typeList = findOrCreateTypeList(parameterList);
typeListOffsettable =
rawDexFile.getOffsetTracker().getOffsettableForItem(typeList);
}
// Search for (or create) the return type.
int returnTypeIdx = findOrCreateTypeId(returnType);
// Search for (or create) the shorty string.
int shortyIdx = findOrCreateString(shorty);
// Create ProtoIdItem.
ProtoIdItem newProtoId = new ProtoIdItem();
newProtoId.shortyIdx = shortyIdx;
newProtoId.returnTypeIdx = returnTypeIdx;
newProtoId.parametersOff = new Offset(false);
if (parameterList.length > 0) {
newProtoId.parametersOff.pointToNew(typeListOffsettable);
}
// ProtoIds must be ordered.
int newProtoIdIdx = findProtoIdInsertionPoint(signature);
rawDexFile.protoIds.add(newProtoIdIdx, newProtoId);
// Insert into OffsetTracker.
if (newProtoIdIdx == 0) {
rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newProtoId, rawDexFile);
} else {
ProtoIdItem prevProtoId = rawDexFile.protoIds.get(newProtoIdIdx - 1);
rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newProtoId, prevProtoId);
}
Log.info(String.format("Created new ProtoIdItem for %s, index: 0x%04x",
signature, newProtoIdIdx));
// Now that we've potentially moved a lot of proto IDs along, all references
// to them need to be updated.
rawDexFile.incrementIndex(IndexUpdateKind.PROTO_ID, newProtoIdIdx);
// All done, return the index for the new proto.
return newProtoIdIdx;
}
private int findProtoId(String signature) {
String shorty = convertSignatureToShorty(signature);
String returnType = convertSignatureToReturnType(signature);
String[] parameterList = convertSignatureToParameterList(signature);
int shortyIdx = findString(shorty);
if (shortyIdx == -1) {
return -1;
}
int returnTypeIdx = findTypeId(returnType);
if (returnTypeIdx == -1) {
return -1;
}
// Only look for a TypeList if there's a parameter list.
TypeList typeList = null;
if (parameterList.length > 0) {
typeList = findTypeList(parameterList);
if (typeList == null) {
return -1;
}
}
int protoIdIdx = 0;
for (ProtoIdItem protoId : rawDexFile.protoIds) {
if (parameterList.length > 0) {
// With parameters.
if (shortyIdx == protoId.shortyIdx
&& returnTypeIdx == protoId.returnTypeIdx
&& typeList.equals(protoId.parametersOff.getPointedToItem())) {
return protoIdIdx;
}
} else {
// Without parameters.
if (shortyIdx == protoId.shortyIdx
&& returnTypeIdx == protoId.returnTypeIdx) {
return protoIdIdx;
}
}
protoIdIdx++;
}
return -1;
}
private int findOrCreateProtoId(String signature) {
int protoIdx = findProtoId(signature);
if (protoIdx != -1) {
return protoIdx;
}
return createProtoId(signature);
}
}