/** * */ package soottocfg.soot.transformers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.google.common.base.Verify; import soot.ArrayType; import soot.IntType; import soot.Local; import soot.Modifier; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Type; import soot.Unit; import soot.Value; import soot.VoidType; import soot.jimple.ArrayRef; import soot.jimple.CastExpr; import soot.jimple.ClassConstant; import soot.jimple.DefinitionStmt; import soot.jimple.FieldRef; import soot.jimple.IdentityStmt; import soot.jimple.InstanceOfExpr; import soot.jimple.IntConstant; import soot.jimple.InvokeExpr; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.LengthExpr; import soot.jimple.NewArrayExpr; import soot.jimple.NewMultiArrayExpr; import soot.jimple.ParameterRef; import soot.jimple.Stmt; import soottocfg.soot.util.SootTranslationHelpers; /** * @author schaef * @author rodykers * The ArrayTransformer iterates over all classes in the Scene * and replaces Java arrays by generated JayArrays. * For each array type A[] we generate a corresponding JayArray type: * class JayArray1 { * public final int size; * public JayArray1(int size); * public A get(int idx); * public void set (int idx, A elem); * } * For multi arrays, the constructor takes one int per dimension. * * Then ArrayTransformer replaces all usages of arrays: * Array reads x=a[i] become x = a.get(i). * Array writes a[i]=x become a.set(i,x). * Array length a.length becomes a.$length. * New array a = new A[1] becomes * a = new JayArrayA; * specialinvoke a.<init>(1); * New multi array a = new A[1][2] becomes * a = new JayArrayA; * specialinvoke a.<init>(1, 2); * * Currently, get and set are not implemented, so the program behavior * is changed. * * Further, the ArrayTransformer changes the signature of main(String[] * args), so * the program cannot be run from main after this transformation. */ public class ArrayTransformer extends AbstractSceneTransformer { public static final String arraySetName = "set"; public static final String arrayGetName = "get"; public static final String arrayTypeName = "JayArray"; public static final String arrayElementPrefix = "atIndex"; private final static String AElem = "_arElem"; // private static final int NumberOfModeledElements = 0; public static boolean isArrayClass(SootClass sc) { return sc.getName().startsWith(arrayTypeName); } public ArrayTransformer() { } private final Map<String, SootField> fieldSubstitutionMap = new HashMap<String, SootField>(); private final Map<String, SootMethod> methodSubstitutionMap = new HashMap<String, SootMethod>(); public void applyTransformation() { /* * We have to do two passes. In the first pass, we update all fields and * method signatures * but not the method bodies. This will break all MethodsRefs and * FieldRefs in the bodies. * In the second pass, we update the body and replace newarray, * newmultiarray, fieldrefs and * lengthexpr by the appropriate expressions. * For the broken FieldRefs and MethodRefs, the toString will not * change, so we can do a * lookup to find the original field/method that we created in the first * pass and create a fresh * refs. */ List<SootClass> classes = new LinkedList<SootClass>(Scene.v().getClasses()); List<JimpleBody> bodies = new LinkedList<JimpleBody>(); List<SootMethod> entryPoints = new LinkedList<SootMethod>(Scene.v().getEntryPoints()); for (SootClass sc : classes) { if (sc.resolvingLevel() >= SootClass.SIGNATURES) { // change the type of all array fields. for (SootField f : sc.getFields()) { if (f.getType() instanceof ArrayType) { final String oldSignature = f.getSignature(); f.setType(arrayTypeToRefType(f.getType())); fieldSubstitutionMap.put(oldSignature, f); } } for (SootMethod sm : sc.getMethods()) { final String oldSignature = sm.getSignature(); // we also have to update the refs in the EntryPoint list. boolean wasMain = sm.isEntryMethod(); if (wasMain) { entryPoints.remove(sm); } if (sc.resolvingLevel() >= SootClass.BODIES && sm.isConcrete() && !sc.isLibraryClass() && !sc.isJavaLibraryClass()) { // record all methods for which we found a body. bodies.add((JimpleBody) sm.retrieveActiveBody()); } // update return type sm.setReturnType(arrayTypeToRefType(sm.getReturnType())); // update parameter types List<Type> newParamTypes = new LinkedList<Type>(); for (Type t : sm.getParameterTypes()) { newParamTypes.add(arrayTypeToRefType(t)); } sm.setParameterTypes(newParamTypes); if (wasMain) { entryPoints.add(sm); } methodSubstitutionMap.put(oldSignature, sm); } } } Scene.v().setEntryPoints(entryPoints); for (JimpleBody body : bodies) { for (Local local : body.getLocals()) { local.setType(arrayTypeToRefType(local.getType())); } // now replace ArrayRefs and NewArray, NewMulitArray // statements. for (Unit u : new LinkedList<Unit>(body.getUnits())) { /* * Changing the types from ArrayType to RefType breaks the soot * 'references', FieldRef, MethodRef, * and ParameterRef since they do not get updated automatically. * Hence, we need to re-build these * Refs by hand: */ if (((Stmt) u).containsFieldRef() && ((Stmt) u).getFieldRef().getType() instanceof ArrayType) { FieldRef fr = ((Stmt) u).getFieldRef(); String sig = fr.getField().getSignature(); Verify.verify(fieldSubstitutionMap.containsKey(sig), "No entry found for " + fr + " in stmt " + u); fr.setFieldRef(fieldSubstitutionMap.get(sig).makeRef()); } else if (((Stmt) u).containsInvokeExpr()) { InvokeExpr ive = ((Stmt) u).getInvokeExpr(); final String oldSignature = ive.getMethodRef().toString(); if (methodSubstitutionMap.containsKey(oldSignature)) { ive.setMethodRef(methodSubstitutionMap.get(oldSignature).makeRef()); } } else if (((Stmt) u) instanceof IdentityStmt && ((IdentityStmt) u).getRightOp() instanceof ParameterRef) { ParameterRef pr = (ParameterRef) ((IdentityStmt) u).getRightOp(); ((IdentityStmt) u).getRightOpBox().setValue(Jimple.v() .newParameterRef(body.getMethod().getParameterType(pr.getIndex()), pr.getIndex())); } if (((Stmt) u).containsArrayRef()) { ArrayRef aref = ((Stmt) u).getArrayRef(); // Note that the baseType has already been replaced, so it // is // a RefType not an ArrayType! RefType refType = (RefType) aref.getBase().getType(); // check if its an array write or read. if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getLeftOp() instanceof ArrayRef) { // replace the a[i]=x by a.set(i,x); SootMethod am = refType.getSootClass().getMethodByName(arraySetName); Stmt ivk = Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr((Local) aref.getBase(), am.makeRef(), Arrays.asList(new Value[] { aref.getIndex(), ((DefinitionStmt) u).getRightOp() }))); ivk.addAllTagsOf(u); // replace u by ivk body.getUnits().insertAfter(ivk, u); body.getUnits().remove(u); } else { SootMethod am = refType.getSootClass().getMethodByName(arrayGetName); // replace the x = a[i] by x = a.get(i) Value ivk = Jimple.v().newVirtualInvokeExpr((Local) aref.getBase(), am.makeRef(), aref.getIndex()); ((Stmt) u).getArrayRefBox().setValue(ivk); } } if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof NewArrayExpr) { NewArrayExpr na = (NewArrayExpr) ((DefinitionStmt) u).getRightOp(); // replace the NewArrayExpr by a NewExpr of // appropriate type. RefType refType = getArrayReplacementType((ArrayType) na.getType()); ((DefinitionStmt) u).getRightOpBox().setValue(Jimple.v().newNewExpr(refType)); // now add a constructor call where we pass the size of the // array. SootClass arrClass = refType.getSootClass(); SootMethod constructor = arrClass.getMethod(SootMethod.constructorName, Arrays.asList(new Type[] { IntType.v() })); Local lhs = (Local) ((DefinitionStmt) u).getLeftOp(); Stmt ccall = Jimple.v() .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(lhs, constructor.makeRef(), na.getSize())); ccall.addAllTagsOf(u); body.getUnits().insertAfter(ccall, u); } else if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof NewMultiArrayExpr) { NewMultiArrayExpr na = (NewMultiArrayExpr) ((DefinitionStmt) u).getRightOp(); RefType refType = getArrayReplacementType((ArrayType) na.getType()); ((DefinitionStmt) u).getRightOpBox().setValue(Jimple.v().newNewExpr(refType)); SootClass arrClass = refType.getSootClass(); List<Type> paramTypes = new ArrayList<Type>( Collections.nCopies(((ArrayType) na.getType()).numDimensions, IntType.v())); SootMethod constructor = arrClass.getMethod(SootMethod.constructorName, paramTypes); List<Value> args = new LinkedList<Value>(na.getSizes()); while (args.size() < paramTypes.size()) { args.add(IntConstant.v(0)); } Local lhs = (Local) ((DefinitionStmt) u).getLeftOp(); Stmt ccall = Jimple.v() .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(lhs, constructor.makeRef(), args)); ccall.addAllTagsOf(u); body.getUnits().insertAfter(ccall, u); } else if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof LengthExpr) { LengthExpr le = (LengthExpr) ((DefinitionStmt) u).getRightOp(); SootClass arrayClass = ((RefType) le.getOp().getType()).getSootClass(); Value fieldRef = Jimple.v().newInstanceFieldRef(le.getOp(), arrayClass.getFieldByName(SootTranslationHelpers.lengthFieldName).makeRef()); ((DefinitionStmt) u).getRightOpBox().setValue(fieldRef); } else if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof InstanceOfExpr) { InstanceOfExpr ioe = (InstanceOfExpr) ((DefinitionStmt) u).getRightOp(); if (ioe.getCheckType() instanceof ArrayType) { ioe.setCheckType(getArrayReplacementType((ArrayType) ioe.getCheckType())); } } else if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof CastExpr) { CastExpr ce = (CastExpr) ((DefinitionStmt) u).getRightOp(); if (ce.getCastType() instanceof ArrayType) { ce.setCastType(getArrayReplacementType((ArrayType) ce.getCastType())); } } else if (u instanceof DefinitionStmt && ((DefinitionStmt) u).getRightOp() instanceof ClassConstant) { ClassConstant cc = (ClassConstant) ((DefinitionStmt) u).getRightOp(); if (cc.getValue().contains("[")) { // TODO, here we have to parse the cc name and // find the corresponding array class. throw new RuntimeException("Not implemented: " + cc); } } } /* * Changing the types of array fields */ for (SootClass sc : classes) { if (sc.resolvingLevel() < SootClass.SIGNATURES) { continue; } for (SootField f : new LinkedList<SootField>(sc.getFields())) { if (f.getType() instanceof ArrayType) { sc.removeField(f); } } } try { body.validate(); } catch (RuntimeException e) { throw e; } } } protected Type arrayTypeToRefType(Type t) { if (t instanceof ArrayType) { return getArrayReplacementType((ArrayType) t); } return t; } private final Map<Type, RefType> arrayTypeMap = new HashMap<Type, RefType>(); protected RefType getArrayReplacementType(ArrayType t) { Type base = arrayTypeToRefType(t.getElementType()); if (!this.arrayTypeMap.containsKey(base)) { SootClass arrClass = createArrayClass(base, t.numDimensions); this.arrayTypeMap.put(base, RefType.v(arrClass)); } return this.arrayTypeMap.get(base); } protected SootClass createArrayClass(Type elementType, int numDimensions) { SootClass arrayClass = new SootClass( arrayTypeName + "_" + elementType.toString().replace(".", "_").replace("$", "D"), Modifier.PUBLIC | Modifier.FINAL); // set the superclass to object arrayClass.setSuperclass(Scene.v().getSootClass("java.lang.Object")); // add the new class to the scene Scene.v().addClass(arrayClass); arrayClass.setResolvingLevel(SootClass.BODIES); arrayClass.setApplicationClass(); // add a field for array.length SootField lengthField = new SootField(SootTranslationHelpers.lengthFieldName, IntType.v(), Modifier.PUBLIC | Modifier.FINAL); arrayClass.addField(lengthField); // type of the array elements (e.g., float for float[]) SootField elemTypeField = new SootField(SootTranslationHelpers.arrayElementTypeFieldName, RefType.v(Scene.v().getSootClass("java.lang.Class")), Modifier.PUBLIC | Modifier.FINAL); arrayClass.addField(elemTypeField); // number of exactly modeled elements int num_exact = soottocfg.Options.v().exactArrayElements(); if (num_exact < 0) num_exact = 0; /** * New array model stuff */ SootField elemField = new SootField(AElem, elementType); if (soottocfg.Options.v().arrayInv()) { arrayClass.addField(elemField); } // create one field for the first N elements of the array which we // model precisely: SootField[] arrFields = new SootField[num_exact]; for (int i = 0; i < num_exact; i++) { arrFields[i] = new SootField(ArrayTransformer.arrayElementPrefix + "_" + i, elementType, Modifier.PUBLIC); arrayClass.addField(arrFields[i]); } /** * GET METHOD */ SootMethod getElement = new SootMethod(arrayGetName, Arrays.asList(new Type[] { IntType.v() }), elementType, Modifier.PUBLIC); arrayClass.addMethod(getElement); JimpleBody body = Jimple.v().newBody(getElement); body.insertIdentityStmts(); Local retLocal = Jimple.v().newLocal("retVal", elementType); body.getLocals().add(retLocal); List<Unit> retStmts = new LinkedList<Unit>(); for (int i = 0; i < num_exact; i++) { Unit ret = Jimple.v().newAssignStmt(retLocal, Jimple.v().newInstanceFieldRef(body.getThisLocal(), arrFields[i].makeRef())); retStmts.add(ret); retStmts.add(Jimple.v().newReturnStmt(retLocal)); Value cond = Jimple.v().newEqExpr(body.getParameterLocal(0), IntConstant.v(i)); body.getUnits().add(Jimple.v().newIfStmt(cond, ret)); } /** * New array model stuff */ if (soottocfg.Options.v().arrayInv()) { // ret = element; return ret; Unit ret = Jimple.v().newAssignStmt(retLocal, Jimple.v().newInstanceFieldRef(body.getThisLocal(), elemField.makeRef())); body.getUnits().add(ret); body.getUnits().add(Jimple.v().newReturnStmt(retLocal)); } else { // if none of the modeled fields was requested, add return havoc as // fall // through case. // ret = havoc; return ret; body.getUnits().add(Jimple.v().newAssignStmt(retLocal, Jimple.v().newStaticInvokeExpr(SootTranslationHelpers.v().getHavocMethod(elementType).makeRef()))); body.getUnits().add(Jimple.v().newReturnStmt(retLocal)); } // now add all the return statements body.getUnits().addAll(retStmts); body.validate(); getElement.setActiveBody(body); /** * SET METHOD */ SootMethod setElement = new SootMethod(arraySetName, Arrays.asList(new Type[] { IntType.v(), elementType }), VoidType.v(), Modifier.PUBLIC); arrayClass.addMethod(setElement); body = Jimple.v().newBody(setElement); body.insertIdentityStmts(); List<Unit> updates = new LinkedList<Unit>(); for (int i = 0; i < num_exact; i++) { Unit asn = Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), arrFields[i].makeRef()), body.getParameterLocal(1)); updates.add(asn); updates.add(Jimple.v().newReturnVoidStmt()); Value cond = Jimple.v().newEqExpr(body.getParameterLocal(0), IntConstant.v(i)); body.getUnits().add(Jimple.v().newIfStmt(cond, asn)); } /** * New array model stuff */ if (soottocfg.Options.v().arrayInv()) { Unit asn = Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), elemField.makeRef()), body.getParameterLocal(1)); body.getUnits().add(asn); } body.getUnits().add(Jimple.v().newReturnVoidStmt()); body.getUnits().addAll(updates); body.validate(); setElement.setActiveBody(body); /** * CONSTRUCTOR */ // Rody: I don't think code below has been maintained. For now, output // warning. /* * Martin: Nope, this is sound AF. Java is loco when it comes to * multi-arrays. * Assume you have an: * int[][][] arr; * you can initialize that with * arr = new int[1][][]; * arr = new int[1][2][]; * or * arr = new int[1][2][3]; * so you need to create one constructor for each dimension ... * a bit annoying, but sound. * */ // if (numDimensions > 1 && !elementType.toString().contains("_java_")) // { // System.err.println("[WARNING] Multi-dimensional arrays not supported. // Result will be unsound."); // } // Now create constructors that takes the array size as input // For int[][][] we have to create 3 constructors since one // could create new int[1][][], new int[1][2][], or new int[1][2][3] // first, add the signatures for all constructors to the class. // this is necessary, because the constructors call each other. SootMethod[] constructors = new SootMethod[numDimensions]; for (int i = 0; i < numDimensions; i++) { List<Type> argTypes = new ArrayList<Type>(Collections.nCopies(i + 1, IntType.v())); SootMethod constructor = new SootMethod(SootMethod.constructorName, argTypes, VoidType.v(), Modifier.PUBLIC); // add the constructor to the class. arrayClass.addMethod(constructor); constructors[i] = constructor; } /* * Now create the bodies for all constructors. Note that this * loop starts from 1 not from 0. */ for (int i = 1; i <= numDimensions; i++) { SootMethod constructor = constructors[i - 1]; body = Jimple.v().newBody(constructor); // add a local for the first param body.insertIdentityStmts(); // set the length field. body.getUnits() .add(Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), lengthField.makeRef()), body.getParameterLocal(0))); // set the element type String elementTypeName = elementType.toString(); if (elementType instanceof RefType) { elementTypeName = ((RefType) elementType).getSootClass().getJavaStyleName(); } elementTypeName = elementTypeName.replace('.', '/'); body.getUnits() .add(Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), elemTypeField.makeRef()), ClassConstant.v(elementTypeName))); /* * Create the statement here, so we can use it * as a jump target for the loop later. */ Stmt returnStmt = Jimple.v().newReturnVoidStmt(); /* * For simple arrays, we initialize all elements to default values * (i.e., zero or null). For multi arrays, we may have to initialize * the elements to new arrays. E.g., * for new int[1][2] we create a constructor call * <init>(1, 2) which contains one element of type int[] and * we have to initialize this to a new array int[2] instead of null. */ if (i > 1) { initializeMultiArrayVars(elementType, body, setElement, constructor, returnStmt); } else { // this is a one dimensional array, so // we can initialize with default values. /** * Old array model stuff */ for (int j = 0; j < num_exact; j++) { Unit asn = Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), arrFields[j].makeRef()), SootTranslationHelpers.v().getDefaultValue(arrFields[j].getType())); body.getUnits().add(asn); } /** * New array model stuff */ if (soottocfg.Options.v().arrayInv()) { // add default initializer for element in new model Unit asn = Jimple.v().newAssignStmt( Jimple.v().newInstanceFieldRef(body.getThisLocal(), elemField.makeRef()), SootTranslationHelpers.v().getDefaultValue(elemField.getType())); body.getUnits().add(asn); } } body.getUnits().add(returnStmt); body.validate(); constructor.setActiveBody(body); } return arrayClass; } private void initializeMultiArrayVars(Type elementType, JimpleBody body, SootMethod setElement, SootMethod constructor, Stmt returnStmt) { /* * Create n objects of the next smaller dimension of * appropriate size. * * ctr = 0; * LoopHead: if (ctr>=param) goto exit; * * this.set( */ Local counter = Jimple.v().newLocal("ctr", IntType.v()); body.getLocals().add(counter); body.getUnits().add(Jimple.v().newAssignStmt(counter, IntConstant.v(0))); Stmt loopHead = Jimple.v().newIfStmt(Jimple.v().newGeExpr(counter, body.getParameterLocal(0)), returnStmt); body.getUnits().add(loopHead); RefType elRefType = (RefType)elementType; Local newElement = Jimple.v().newLocal("elem", elRefType); body.getLocals().add(newElement); //create a new object body.getUnits().add(Jimple.v().newAssignStmt(newElement, Jimple.v().newNewExpr(elRefType))); //the elements have one dimension less than the current one. int elParamCount = constructor.getParameterCount()-1; //call the constructor List<Type> parameterTypes = new ArrayList<Type>(Collections.nCopies(elParamCount, IntType.v())); SootMethod elConstructor = elRefType.getSootClass().getMethod(SootMethod.constructorName, parameterTypes, VoidType.v()); List<Value> elConstructorArgs = new LinkedList<Value>(); for (int k=1; k<constructor.getParameterCount();k++) { elConstructorArgs.add(body.getParameterLocal(k)); } body.getUnits().add( Jimple.v().newInvokeStmt( Jimple.v().newSpecialInvokeExpr(newElement, elConstructor.makeRef(), elConstructorArgs) )); //update the current field to that new object. List<Value> args = new LinkedList<Value>(); args.add(counter); args.add(newElement); body.getUnits().add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(body.getThisLocal(), setElement.makeRef(), args))); body.getUnits().add(Jimple.v().newAssignStmt(counter, Jimple.v().newAddExpr(counter, IntConstant.v(1)))); body.getUnits().add(Jimple.v().newGotoStmt(loopHead)); } }