diff --git a/src/main/java/soot/dotnet/instructions/AbstractNewObjInstanceInstruction.java b/src/main/java/soot/dotnet/instructions/AbstractNewObjInstanceInstruction.java index 7951afe6ec9..264e5d7dc47 100644 --- a/src/main/java/soot/dotnet/instructions/AbstractNewObjInstanceInstruction.java +++ b/src/main/java/soot/dotnet/instructions/AbstractNewObjInstanceInstruction.java @@ -56,7 +56,7 @@ import soot.jimple.Jimple; import soot.jimple.SpecialInvokeExpr; -public abstract class AbstractNewObjInstanceInstruction extends AbstractCilnstruction { +public abstract class AbstractNewObjInstanceInstruction extends CilCallInstruction { public AbstractNewObjInstanceInstruction(ProtoIlInstructions.IlInstructionMsg instruction, DotnetBody dotnetBody, CilBlock cilBlock) { super(instruction, dotnetBody, cilBlock); diff --git a/src/main/java/soot/dotnet/instructions/CilCallInstruction.java b/src/main/java/soot/dotnet/instructions/CilCallInstruction.java index 99d460ef3e8..a827983a7f0 100644 --- a/src/main/java/soot/dotnet/instructions/CilCallInstruction.java +++ b/src/main/java/soot/dotnet/instructions/CilCallInstruction.java @@ -23,7 +23,9 @@ */ import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /*- * #%L @@ -54,8 +56,10 @@ import soot.SootClass; import soot.SootMethodRef; import soot.Type; +import soot.Unit; import soot.Value; import soot.dotnet.members.AbstractDotnetMember; +import soot.dotnet.members.ByReferenceWrapperGenerator; import soot.dotnet.members.DotnetMethod; import soot.dotnet.members.method.DotnetBody; import soot.dotnet.proto.ProtoAssemblyAllTypes; @@ -111,61 +115,36 @@ public void jimplify(Body jb) { */ @Override public Value jimplifyExpr(Body jb) { - clazz = Scene.v().getSootClass(instruction.getMethod().getDeclaringType().getFullname()); - method = new DotnetMethod(instruction.getMethod(), clazz); + SootClass clazz = Scene.v().getSootClass(instruction.getMethod().getDeclaringType().getFullname()); + DotnetMethod method = new DotnetMethod(instruction.getMethod(), clazz); // STATIC if (method.isStatic()) { - checkMethodAvailable(); - List argsVariables = new ArrayList<>(instruction.getArgumentsCount()); - List argsTypes = new ArrayList<>(method.getParameterDefinitions().size()); - + checkMethodAvailable(method); // If System.Array.Empty Value rewriteField = AbstractDotnetMember.checkRewriteCilSpecificMember(clazz, method.getName()); if (rewriteField != null) { return rewriteField; } + MethodParams methodParams = getMethodCallParams(method, false, jb); - // method-parameters (signature) - for (ProtoAssemblyAllTypes.ParameterDefinition parameterDefinition : method.getParameterDefinitions()) { - argsTypes.add(DotnetTypeFactory.toSootType(parameterDefinition.getType())); - } - - int i = 0; - // arguments which are passed to this function - for (ProtoIlInstructions.IlInstructionMsg a : instruction.getArgumentsList()) { - Value argValue = CilInstructionFactory.fromInstructionMsg(a, dotnetBody, cilBlock).jimplifyExpr(jb); - argValue = simplifyComplexExpression(jb, argValue); - Type argDestType = argsTypes.get(i); - if (argDestType instanceof RefType && argValue.getType() instanceof PrimType) { - // can happen when enums are expected - argValue = createTempVar(jb, Jimple.v(), Jimple.v().newCastExpr(argValue, argDestType)); - } - argsVariables.add(argValue); - i++; - } - - String methodName = method.getUniqueName(); - - SootMethodRef methodRef = Scene.v().makeMethodRef(clazz, DotnetMethod.convertCtorName(methodName), argsTypes, - DotnetTypeFactory.toSootType(method.getReturnType()), true); - return Jimple.v().newStaticInvokeExpr(methodRef, argsVariables); + return Jimple.v().newStaticInvokeExpr(methodParams.methodRef, methodParams.argumentVariables); } // INTERFACE else if (clazz.isInterface()) { - MethodParams methodParams = getMethodCallParams(jb); - return Jimple.v().newInterfaceInvokeExpr(methodParams.Base, methodParams.MethodRef, methodParams.ArgumentVariables); + MethodParams methodParams = getMethodCallParams(method, true, jb); + return Jimple.v().newInterfaceInvokeExpr(methodParams.base, methodParams.methodRef, methodParams.argumentVariables); } // CONSTRUCTOR and PRIVATE METHOD CALLS else if (instruction.getMethod().getIsConstructor() || instruction.getMethod().getAccessibility().equals(ProtoAssemblyAllTypes.Accessibility.PRIVATE)) { - MethodParams methodParams = getMethodCallParams(jb); - return Jimple.v().newSpecialInvokeExpr(methodParams.Base, methodParams.MethodRef, methodParams.ArgumentVariables); + MethodParams methodParams = getMethodCallParams(method, true, jb); + return Jimple.v().newSpecialInvokeExpr(methodParams.base, methodParams.methodRef, methodParams.argumentVariables); } // DYNAMIC OBJECT METHOD else { - MethodParams methodParams = getMethodCallParams(jb); - return Jimple.v().newVirtualInvokeExpr(methodParams.Base, methodParams.MethodRef, methodParams.ArgumentVariables); + MethodParams methodParams = getMethodCallParams(method, true, jb); + return Jimple.v().newVirtualInvokeExpr(methodParams.base, methodParams.methodRef, methodParams.argumentVariables); } } @@ -175,35 +154,56 @@ public List> getLocalsToCastForCall() { private final List> localsToCastForCall; - private MethodParams getMethodCallParams(Body jb) { - checkMethodAvailable(); + protected MethodParams getMethodCallParams(boolean hasBase, Body jb) { + SootClass clazz = Scene.v().getSootClass(instruction.getMethod().getDeclaringType().getFullname()); + DotnetMethod method = new DotnetMethod(instruction.getMethod(), clazz); + return getMethodCallParams(method, hasBase, jb); + } + + protected MethodParams getMethodCallParams(DotnetMethod method, boolean hasBase, Body jb) { + checkMethodAvailable(method); List argsVariables = new ArrayList<>(); List methodParamTypes = new ArrayList<>(); - if (instruction.getArgumentsCount() == 0) { + if (hasBase && instruction.getArgumentsCount() == 0) { throw new RuntimeException("Opcode: " + instruction.getOpCode() + ": Given method " + method.getName() + " of declared type " + method.getDeclaringClass().getName() + " has no arguments! This means there is no base variable for the virtual invoke!"); } - Value baseValue - = CilInstructionFactory.fromInstructionMsg(instruction.getArguments(0), dotnetBody, cilBlock).jimplifyExpr(jb); - baseValue = simplifyComplexExpression(jb, baseValue); - if (baseValue instanceof Constant) { - baseValue = createTempVar(jb, Jimple.v(), baseValue); + int startIdx = 0; + Local base = null; + if (hasBase) { + Value baseValue + = CilInstructionFactory.fromInstructionMsg(instruction.getArguments(0), dotnetBody, cilBlock).jimplifyExpr(jb); + baseValue = simplifyComplexExpression(jb, baseValue); + if (baseValue instanceof Constant) { + baseValue = createTempVar(jb, Jimple.v(), baseValue); + } + base = (Local) baseValue; + startIdx = 1; } - Local base = (Local) baseValue; - if (instruction.getArgumentsCount() > 1) { - for (int z = 1; z < instruction.getArgumentsCount(); z++) { + Map byRefWrappers = new HashMap<>(); + if (instruction.getArgumentsCount() > startIdx) { + for (int z = startIdx; z < instruction.getArgumentsCount(); z++) { Value argValue = CilInstructionFactory.fromInstructionMsg(instruction.getArguments(z), dotnetBody, cilBlock).jimplifyExpr(jb); argValue = simplifyComplexExpression(jb, argValue); argsVariables.add(argValue); } + int p = 0; for (ProtoAssemblyAllTypes.ParameterDefinition parameterDefinition : instruction.getMethod().getParameterList()) { - methodParamTypes.add(DotnetTypeFactory.toSootType(parameterDefinition.getType())); + Type t = DotnetTypeFactory.toSootType(parameterDefinition.getType()); + if (ByReferenceWrapperGenerator.needsWrapper(parameterDefinition)) { + SootClass sc = ByReferenceWrapperGenerator.getWrapperClass(t); + t = sc.getType(); + byRefWrappers.put(p, sc); + } + methodParamTypes.add(t); + p++; } } + List afterCallUnits = null; // Check if cast is needed for correct validation, e.g.: // System.Object modifiers = null; @@ -211,16 +211,31 @@ private MethodParams getMethodCallParams(Body jb) { // GetConstructor(System.Reflection.ParameterModifier[])>(modifiers); // FastHierarchy is not available at this point, check hierarchy would be easier for (int i = 0; i < argsVariables.size(); i++) { - Value arg = argsVariables.get(i); + final Value originalArg = argsVariables.get(i); + Value modifiedArg = originalArg; Type methodParam = methodParamTypes.get(i); - if (arg instanceof Local) { - if (arg.getType().toString().equals(DotnetBasicTypes.SYSTEM_OBJECT) + Type argType = modifiedArg.getType(); + if (modifiedArg instanceof Local) { + if (argType.toString().equals(DotnetBasicTypes.SYSTEM_OBJECT) && !methodParam.toString().equals(DotnetBasicTypes.SYSTEM_OBJECT)) { Local castLocal = dotnetBody.variableManager.localGenerator.generateLocal(methodParam); - localsToCastForCall.add(new Pair<>((Local) arg, castLocal)); - argsVariables.set(i, castLocal); + localsToCastForCall.add(new Pair<>((Local) modifiedArg, castLocal)); + modifiedArg = castLocal; + } + } + if (methodParam instanceof RefType && argType instanceof PrimType) { + // can happen when enums are expected + modifiedArg = createTempVar(jb, Jimple.v(), Jimple.v().newCastExpr(modifiedArg, methodParam)); + } + SootClass wrapped = byRefWrappers.get(i); + if (wrapped != null) { + modifiedArg = ByReferenceWrapperGenerator.insertWrapperCall(jb, wrapped, modifiedArg); + if (afterCallUnits == null) { + afterCallUnits = new ArrayList<>(); } + afterCallUnits.add(ByReferenceWrapperGenerator.getUnwrapCall(wrapped, modifiedArg, originalArg)); } + argsVariables.set(i, modifiedArg); } String methodName = method.getUniqueName(); @@ -235,28 +250,30 @@ private MethodParams getMethodCallParams(Body jb) { return_type = DotnetTypeFactory.toSootType(method.getProtoMessage().getReturnType()); } - SootMethodRef methodRef - = Scene.v().makeMethodRef(clazz, DotnetMethod.convertCtorName(methodName), methodParamTypes, return_type, false); + SootMethodRef methodRef = Scene.v().makeMethodRef(method.getDeclaringClass(), DotnetMethod.convertCtorName(methodName), + methodParamTypes, return_type, !hasBase); - return new MethodParams(base, methodRef, argsVariables); + return new MethodParams(base, methodRef, argsVariables, afterCallUnits); } - private void checkMethodAvailable() { + private void checkMethodAvailable(DotnetMethod method) { if (method.getName().trim().isEmpty()) { throw new RuntimeException("Opcode: " + instruction.getOpCode() + ": Given method " + method.getName() + " of declared type " + method.getDeclaringClass().getName() + " has no method name!"); } } - private static class MethodParams { - public MethodParams(Local base, SootMethodRef methodRef, List argsVariables) { - Base = base; - MethodRef = methodRef; - ArgumentVariables = argsVariables; + static class MethodParams { + public MethodParams(Local base, SootMethodRef methodRef, List argsVariables, List afterCallUnits) { + this.base = base; + this.methodRef = methodRef; + this.argumentVariables = argsVariables; + this.afterCallUnits = afterCallUnits; } - public Local Base; - public SootMethodRef MethodRef; - public List ArgumentVariables; + public Local base; + public SootMethodRef methodRef; + public List argumentVariables; + private List afterCallUnits; } } diff --git a/src/main/java/soot/dotnet/instructions/CilNewObjInstruction.java b/src/main/java/soot/dotnet/instructions/CilNewObjInstruction.java index 95ef1b17867..aed593d6a82 100644 --- a/src/main/java/soot/dotnet/instructions/CilNewObjInstruction.java +++ b/src/main/java/soot/dotnet/instructions/CilNewObjInstruction.java @@ -1,7 +1,5 @@ package soot.dotnet.instructions; -import java.util.ArrayList; - /*- * #%L * Soot - a J*va Optimization Framework @@ -24,24 +22,14 @@ * #L% */ import soot.Body; -import soot.PrimType; import soot.RefType; import soot.Scene; import soot.SootClass; -import soot.Type; import soot.Value; import soot.dotnet.members.method.DotnetBody; -import soot.dotnet.proto.ProtoAssemblyAllTypes.IlType; -import soot.dotnet.proto.ProtoAssemblyAllTypes.ParameterDefinition; import soot.dotnet.proto.ProtoIlInstructions; -import soot.dotnet.types.DotnetTypeFactory; -import soot.jimple.DoubleConstant; -import soot.jimple.FloatConstant; -import soot.jimple.IntConstant; import soot.jimple.Jimple; -import soot.jimple.LongConstant; import soot.jimple.NewExpr; -import soot.jimple.StringConstant; /** * Combi instruction with instantiating a new object and calling the constructor (no structs often) Call @@ -64,60 +52,11 @@ public Value jimplifyExpr(Body jb) { SootClass clazz = RefType.v(instruction.getMethod().getDeclaringType().getFullname()).getSootClass(); NewExpr newExpr = Jimple.v().newNewExpr(clazz.getType()); - ArrayList argsTypes = new ArrayList<>(instruction.getMethod().getParameterCount()); - for (ParameterDefinition p : instruction.getMethod().getParameterList()) { - argsTypes.add(DotnetTypeFactory.toSootType(p.getType())); - } - ArrayList argsVariables = new ArrayList<>(instruction.getArgumentsList().size()); - int i = 0; - for (ProtoIlInstructions.IlInstructionMsg a : instruction.getArgumentsList()) { - - if (a.hasVariable()) { - argsVariables.add(dotnetBody.variableManager.addOrGetVariable(a.getVariable(), jb)); - } else { - IlType t = a.getConstantType(); - if (t == null) { - throw new RuntimeException("Not a local or constant: " + a); - } - Value argValue; - switch (t) { - case type_unknown: - case UNRECOGNIZED: - Value v = CilInstructionFactory.fromInstructionMsg(a, dotnetBody, cilBlock).jimplifyExpr(jb); - argValue = createTempVar(jb, Jimple.v(), v); - break; - case type_double: - argValue = DoubleConstant.v(a.getValueConstantDouble()); - break; - case type_float: - argValue = FloatConstant.v(a.getValueConstantFloat()); - break; - case type_int32: - argValue = IntConstant.v((int) a.getValueConstantInt64()); - break; - case type_int64: - argValue = LongConstant.v(a.getValueConstantInt64()); - break; - case type_string: - argValue = StringConstant.v(a.getValueConstantString()); - break; - default: - throw new RuntimeException("Unsupported: " + t); - - } - Type argDestType = argsTypes.get(i); - if (argDestType instanceof RefType && argValue.getType() instanceof PrimType) { - // can happen when enums are expected - argValue = createTempVar(jb, Jimple.v(), Jimple.v().newCastExpr(argValue, argDestType)); - } - argsVariables.add(argValue); - i++; - } - } + MethodParams t = getMethodCallParams(false, jb); // Constructor call expression - methodRef = Scene.v().makeConstructorRef(clazz, argsTypes); - listOfArgs = argsVariables; + methodRef = Scene.v().makeConstructorRef(clazz, t.methodRef.getParameterTypes()); + listOfArgs = t.argumentVariables; return newExpr; } diff --git a/src/main/java/soot/dotnet/members/ByReferenceWrapperGenerator.java b/src/main/java/soot/dotnet/members/ByReferenceWrapperGenerator.java new file mode 100644 index 00000000000..1aca84ab42e --- /dev/null +++ b/src/main/java/soot/dotnet/members/ByReferenceWrapperGenerator.java @@ -0,0 +1,96 @@ +package soot.dotnet.members; + +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import soot.Body; +import soot.Local; +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.dotnet.proto.ProtoAssemblyAllTypes.ParameterDefinition; +import soot.jimple.Jimple; +import soot.jimple.JimpleBody; + +/** + * Parameters in .NET can be passed by reference. Since Jimple does not support this semantic, we generate Wrapper classes to + * pass instead. + * + * Note that .NET (as of right now, mid 2024) does not support co- or contravariants in parameters and return types. As such, + * overriding virtual methods does not pose a problem when we substitute parameters with reference wrappers. + * + * @author Marc Miltenberger + */ +public class ByReferenceWrapperGenerator { + public synchronized static SootClass getWrapperClass(Type t) { + String name = "ByReferenceWrappers." + t.toString(); + Scene scene = Scene.v(); + SootClass sc = scene.getSootClassUnsafe(name); + if (sc != null) { + return sc; + } + sc = scene.makeSootClass(name, Modifier.FINAL | Modifier.STATIC); + sc.setApplicationClass(); + SootField r = scene.makeSootField("r", t); + r.setModifiers(Modifier.PUBLIC); + sc.addField(r); + + SootMethod ctor = scene.makeSootMethod("", Arrays.asList(t), VoidType.v()); + Jimple j = Jimple.v(); + JimpleBody b = j.newBody(ctor); + ctor.setActiveBody(b); + b.insertIdentityStmts(); + b.getUnits().add(j.newAssignStmt(j.newInstanceFieldRef(b.getThisLocal(), r.makeRef()), b.getParameterLocal(0))); + b.getUnits().add(j.newReturnVoidStmt()); + + sc.addMethod(ctor); + return sc; + } + + public static boolean needsWrapper(ParameterDefinition parameter) { + return parameter.getIsIn() || parameter.getIsOut() || parameter.getIsRef(); + } + + /** + * Inserts calls to wrap objects + * + * @param jb + * the body to insert wrapper calls + * @param wrapperClass + * the class to use for wrapping + * @param arg + * the argument to wrap + * @return the wrapped argument + */ + public static Value insertWrapperCall(Body jb, SootClass wrapperClass, Value arg) { + SootMethod mCtor = wrapperClass.getMethodByName(""); + Jimple j = Jimple.v(); + Local l = j.newLocal("wrap", wrapperClass.getType()); + jb.getLocals().add(l); + jb.getUnits().add(j.newAssignStmt(l, j.newNewExpr(wrapperClass.getType()))); + jb.getUnits().add(j.newInvokeStmt(j.newSpecialInvokeExpr(l, mCtor.makeRef(), arg))); + return l; + } + + /** + * Insert the call to unwrap objects + * + * @param wrapperClass + * the class to use for wrapping + * @param argToUnwrap + * the argument to unwrap + * @param target + * the target that should contain the target afterwards + * @return the unwrapper statement + */ + public static Unit getUnwrapCall(SootClass wrapperClass, Value argToUnwrap, Value target) { + SootField f = wrapperClass.getFieldByName("r"); + Jimple j = Jimple.v(); + return j.newAssignStmt(target, j.newInstanceFieldRef(argToUnwrap, f.makeRef())); + } +} diff --git a/src/main/java/soot/dotnet/members/DotnetMethod.java b/src/main/java/soot/dotnet/members/DotnetMethod.java index f9bb79c377d..39acd112ca5 100644 --- a/src/main/java/soot/dotnet/members/DotnetMethod.java +++ b/src/main/java/soot/dotnet/members/DotnetMethod.java @@ -377,16 +377,20 @@ public String getUniqueName() { * @return java constructor name */ public static String convertCtorName(String methodName) { - methodName = methodName.replace(CONSTRUCTOR_NAME, JAVA_CONSTRUCTOR_NAME); - methodName = methodName.replace(STATIC_CONSTRUCTOR_NAME, JAVA_STATIC_CONSTRUCTOR_NAME); - methodName = methodName.replace("+", "$"); - return methodName; - } - - public static String convertCilToJvmNaming(String methodName) { + if (methodName.equals(CONSTRUCTOR_NAME)) { + return JAVA_CONSTRUCTOR_NAME; + } + if (methodName.equals(STATIC_CONSTRUCTOR_NAME)) { + return JAVA_STATIC_CONSTRUCTOR_NAME; + } + if (methodName.endsWith(CONSTRUCTOR_NAME)) { + methodName = methodName.substring(0, methodName.length() - CONSTRUCTOR_NAME.length()) + JAVA_CONSTRUCTOR_NAME; + } + if (methodName.endsWith(STATIC_CONSTRUCTOR_NAME)) { + methodName + = methodName.substring(0, methodName.length() - STATIC_CONSTRUCTOR_NAME.length()) + JAVA_STATIC_CONSTRUCTOR_NAME; + } methodName = methodName.replace("+", "$"); - methodName = methodName.replace(JAVA_CONSTRUCTOR_NAME, CONSTRUCTOR_NAME); - methodName = methodName.replace(JAVA_STATIC_CONSTRUCTOR_NAME, STATIC_CONSTRUCTOR_NAME); return methodName; } diff --git a/src/main/java/soot/dotnet/members/method/DotnetMethodParameter.java b/src/main/java/soot/dotnet/members/method/DotnetMethodParameter.java index 14793175500..c4617aaf66f 100644 --- a/src/main/java/soot/dotnet/members/method/DotnetMethodParameter.java +++ b/src/main/java/soot/dotnet/members/method/DotnetMethodParameter.java @@ -26,6 +26,7 @@ */ import soot.Type; +import soot.dotnet.members.ByReferenceWrapperGenerator; import soot.dotnet.proto.ProtoAssemblyAllTypes; import soot.dotnet.types.DotnetTypeFactory; @@ -44,6 +45,9 @@ public static List toSootTypeParamsList(List types = new ArrayList<>(); for (ProtoAssemblyAllTypes.ParameterDefinition parameter : parameterList) { Type type = DotnetTypeFactory.toSootType(parameter.getType()); + if (ByReferenceWrapperGenerator.needsWrapper(parameter)) { + type = ByReferenceWrapperGenerator.getWrapperClass(type).getType(); + } types.add(type); } return types; diff --git a/tutorial/jimpleParser/jimpleParser.tex b/tutorial/jimpleParser/jimpleParser.tex index 6027ae9c38a..f290d094017 100644 --- a/tutorial/jimpleParser/jimpleParser.tex +++ b/tutorial/jimpleParser/jimpleParser.tex @@ -29,6 +29,8 @@ \section{Introduction} Finally, a user, or a software tool, can write a class in the Jimple textual format, use Soot to parse the Jimple, and thus create bytecode. +\todo{trade off: precision and recall} + The Jimple format is self-explanatory. The SableCC grammar for textual Jimple can be found in the source code distribution of Soot; it is called {\tt jimple.scc}. The following is an fragment of Jimple code: