mirror of
https://github.com/revanced/smali.git
synced 2025-05-28 11:50:12 +02:00
Implemented verification for filled-new-array
git-svn-id: https://smali.googlecode.com/svn/trunk@575 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
parent
fffb29fd9d
commit
9e5dd85d83
@ -238,10 +238,31 @@ public class ClassPath {
|
||||
arrayDimensions = i;
|
||||
}
|
||||
|
||||
public ClassDef getElementClass() {
|
||||
/**
|
||||
* Returns the "base" element class of the array.
|
||||
*
|
||||
* For example, for a multi-dimensional array of strings ([[Ljava/lang/String;), this method would return
|
||||
* Ljava/lang/String;
|
||||
* @return
|
||||
*/
|
||||
public ClassDef getBaseElementClass() {
|
||||
return elementClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "immediate" element class of the array.
|
||||
*
|
||||
* For example, for a multi-dimensional array of stings with 2 dimensions ([[Ljava/lang/String;), this method
|
||||
* would return [Ljava/lang/String;
|
||||
* @return
|
||||
*/
|
||||
public ClassDef getImmediateElementClass() {
|
||||
if (arrayDimensions == 1) {
|
||||
return elementClass;
|
||||
}
|
||||
return getArrayClassDefByElementClassAndDimension(elementClass, arrayDimensions - 1);
|
||||
}
|
||||
|
||||
public int getArrayDimensions() {
|
||||
return arrayDimensions;
|
||||
}
|
||||
|
@ -458,6 +458,8 @@ public class MethodAnalyzer {
|
||||
return handleNewInstance(analyzedInstruction);
|
||||
case NEW_ARRAY:
|
||||
return handleNewArray(analyzedInstruction);
|
||||
case FILLED_NEW_ARRAY:
|
||||
return handleFilledNewArray(analyzedInstruction);
|
||||
}
|
||||
assert false;
|
||||
return false;
|
||||
@ -946,6 +948,59 @@ public class MethodAnalyzer {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleFilledNewArray(AnalyzedInstruction analyzedInstruction) {
|
||||
RegisterType arrayType;
|
||||
RegisterType arrayImmediateElementType;
|
||||
{
|
||||
InstructionWithReference instruction = (InstructionWithReference)analyzedInstruction.instruction;
|
||||
|
||||
Item item = instruction.getReferencedItem();
|
||||
assert item.getItemType() == ItemType.TYPE_TYPE_ID_ITEM;
|
||||
|
||||
ClassPath.ClassDef classDef = ClassPath.getClassDef((TypeIdItem)item);
|
||||
|
||||
if (classDef.getClassType().charAt(0) != '[') {
|
||||
throw new ValidationException("Cannot use non-array type \"" + classDef.getClassType() +
|
||||
"\" with new-array. Use new-instance instead.");
|
||||
}
|
||||
|
||||
ClassPath.ArrayClassDef arrayClassDef = (ClassPath.ArrayClassDef)classDef;
|
||||
arrayType = RegisterType.getRegisterType(RegisterType.Category.Reference, classDef);
|
||||
arrayImmediateElementType = RegisterType.getRegisterTypeForType(
|
||||
arrayClassDef.getImmediateElementClass().getClassType());
|
||||
String baseElementType = arrayClassDef.getBaseElementClass().getClassType();
|
||||
if (baseElementType.charAt(0) == 'J' || baseElementType.charAt(0) == 'D') {
|
||||
throw new ValidationException("Cannot use filled-new-array to create an array of wide values " +
|
||||
"(long or double)");
|
||||
}
|
||||
}
|
||||
|
||||
FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.instruction;
|
||||
int registerCount = instruction.getRegCount();
|
||||
|
||||
byte[] registers = new byte[]{instruction.getRegisterD(), instruction.getRegisterE(), instruction.getRegisterF(),
|
||||
instruction.getRegisterG(), instruction.getRegisterA()};
|
||||
|
||||
for (int i=0; i<registerCount; i++) {
|
||||
int register = registers[i];
|
||||
RegisterType elementType = analyzedInstruction.getPreInstructionRegisterType(register);
|
||||
assert elementType != null;
|
||||
|
||||
if (elementType.category == RegisterType.Category.Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!elementType.canBeAssignedTo(arrayImmediateElementType)) {
|
||||
throw new ValidationException("Register v" + Integer.toString(register) + " is of type " +
|
||||
elementType.toString() + " and is incompatible with the array type " +
|
||||
arrayType.type.getClassType());
|
||||
}
|
||||
}
|
||||
|
||||
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, arrayType);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void checkRegister(RegisterType registerType, EnumSet validCategories) {
|
||||
if (!validCategories.contains(registerType.category)) {
|
||||
//TODO: add expected categories to error message
|
||||
|
@ -48,6 +48,7 @@ public class RegisterType {
|
||||
}
|
||||
|
||||
public static enum Category {
|
||||
//the Unknown category denotes a register type that hasn't been determined yet
|
||||
Unknown,
|
||||
Null,
|
||||
One,
|
||||
@ -63,10 +64,19 @@ public class RegisterType {
|
||||
LongHi,
|
||||
DoubleLo,
|
||||
DoubleHi,
|
||||
//the UninitRef category is used after a new-instance operation, and before the corresponding <init> is called
|
||||
//it is also used for the "this" register inside an <init> method, before the superclass' <init> method is
|
||||
//called
|
||||
UninitRef,
|
||||
Reference,
|
||||
//This is used when there are multiple incoming execution paths that have incompatible register types. For
|
||||
//example if the register's type is an Integer on one incomming code path, but is a Reference type on another
|
||||
//incomming code path. There is no register type that can hold either an Integer or a Reference.
|
||||
Conflicted;
|
||||
|
||||
//this table is used when merging register types. For example, if a particular register can be either a Byte
|
||||
//or a Char, then the "merged" type of that register would be Integer, because it is the "smallest" type can
|
||||
//could hold either type of value.
|
||||
protected static Category[][] mergeTable =
|
||||
{
|
||||
/* Unknown Null One, Boolean Byte PosByte Short PosShort Char Integer, Float, LongLo LongHi DoubleLo DoubleHi UninitRef Reference Conflicted*/
|
||||
@ -89,10 +99,41 @@ public class RegisterType {
|
||||
/*Reference*/ {Unknown, Reference, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Reference, Conflicted},
|
||||
/*Conflicted*/ {Unknown, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}
|
||||
};
|
||||
|
||||
//this table is used to denote whether a given value type can be assigned to a "slot" of a certain type. For
|
||||
//example, to determine if you can assign a Boolean value to a particular array "slot", where the array is an
|
||||
//array of Integers, you would look up assignmentTable[Boolean.ordinal()][Integer.ordinal()]
|
||||
//Note that not all slot types in the table are expected to be used. For example, it doesn't make sense to
|
||||
//check if a value can be assigned to an uninitialized reference slot - because there is no such thing.
|
||||
protected static boolean[][] assigmentTable =
|
||||
{
|
||||
/* Unknown Null One, Boolean Byte PosByte Short PosShort Char Integer, Float, LongLo LongHi DoubleLo DoubleHi UninitRef Reference Conflicted |slot type*/
|
||||
/*Unknown*/ {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
|
||||
/*Null*/ {false, true, false, true, true, true, true, true, true, true, true, false, false, false, false, false, true, false},
|
||||
/*One*/ {false, false, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false},
|
||||
/*Boolean*/ {false, false, false, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false},
|
||||
/*Byte*/ {false, false, false, false, true, false, true, true, false, true, true, false, false, false, false, false, false, false},
|
||||
/*PosByte*/ {false, false, false, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false},
|
||||
/*Short*/ {false, false, false, false, false, false, true, false, false, true, true, false, false, false, false, false, false, false},
|
||||
/*PosShort*/ {false, false, false, false, false, false, true, true, true, true, true, false, false, false, false, false, false, false},
|
||||
/*Char*/ {false, false, false, false, false, false, false, false, true, true, true, false, false, false, false, false, false, false},
|
||||
/*Integer*/ {false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false},
|
||||
/*Float*/ {false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false},
|
||||
/*LongLo*/ {false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, false, false, false},
|
||||
/*LongHi*/ {false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, false, false},
|
||||
/*DoubleLo*/ {false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, false, false, false},
|
||||
/*DoubleHi*/ {false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, false, false},
|
||||
/*UninitRef*/ {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false},
|
||||
/*Reference*/ {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false},
|
||||
/*Conflicted*/ {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}
|
||||
/*----------*/
|
||||
/*value type*/
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public static RegisterType getRegisterTypeForTypeIdItem(TypeIdItem typeIdItem) {
|
||||
switch (typeIdItem.getTypeDescriptor().charAt(0)) {
|
||||
public static RegisterType getRegisterTypeForType(String type) {
|
||||
switch (type.charAt(0)) {
|
||||
case 'V':
|
||||
throw new ValidationException("The V type can only be used as a method return type");
|
||||
case 'Z':
|
||||
@ -113,12 +154,16 @@ public class RegisterType {
|
||||
return getRegisterType(Category.DoubleLo, null);
|
||||
case 'L':
|
||||
case '[':
|
||||
return getRegisterType(Category.Reference, ClassPath.getClassDef(typeIdItem));
|
||||
return getRegisterType(Category.Reference, ClassPath.getClassDef(type));
|
||||
default:
|
||||
throw new RuntimeException("Invalid type: " + typeIdItem.getTypeDescriptor());
|
||||
throw new RuntimeException("Invalid type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public static RegisterType getRegisterTypeForTypeIdItem(TypeIdItem typeIdItem) {
|
||||
return getRegisterTypeForType(typeIdItem.getTypeDescriptor());
|
||||
}
|
||||
|
||||
public static RegisterType getWideRegisterTypeForTypeIdItem(TypeIdItem typeIdItem, boolean firstRegister) {
|
||||
if (typeIdItem.getRegisterCount() == 1) {
|
||||
throw new RuntimeException("Cannot use this method for non-wide register type: " +
|
||||
@ -192,6 +237,20 @@ public class RegisterType {
|
||||
return RegisterType.getRegisterType(mergedCategory, mergedType);
|
||||
}
|
||||
|
||||
public boolean canBeAssignedTo(RegisterType slotType) {
|
||||
if (Category.assigmentTable[this.category.ordinal()][slotType.category.ordinal()]) {
|
||||
if (this.category == Category.Reference && slotType.category == Category.Reference) {
|
||||
if (!slotType.type.isInterface()) {
|
||||
return this.type.extendsClass(slotType.type);
|
||||
}
|
||||
//for verification, we assume all objects implement all interfaces, so we don't verify the type if
|
||||
//slotType is an interface
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static RegisterType getRegisterType(Category category, ClassDef classType) {
|
||||
RegisterType newRegisterType = new RegisterType(category, classType);
|
||||
RegisterType internedRegisterType = internedRegisterTypes.get(newRegisterType);
|
||||
|
Loading…
x
Reference in New Issue
Block a user