From 7bb46b9082b7b1c2527c452b4f87711605790f5b Mon Sep 17 00:00:00 2001 From: Steven Arzt Date: Fri, 24 May 2024 14:36:31 +0200 Subject: [PATCH 1/4] fixed a corner case with any_subtype_of and phantom classes --- src/main/java/soot/FastHierarchy.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/soot/FastHierarchy.java b/src/main/java/soot/FastHierarchy.java index 11eec9ad900..5058e9368bd 100644 --- a/src/main/java/soot/FastHierarchy.java +++ b/src/main/java/soot/FastHierarchy.java @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -307,8 +306,16 @@ public boolean canStoreType(final Type child, final Type parent) { // From Java Language Spec 2nd ed., Chapter 10, Arrays return base == rtObject || base == rtSerializable || base == rtCloneable; } else { + // We can story any_subtype_of(x) in a variable of type x + RefType childBase = ((AnySubType) child).getBase(); + if (childBase == parent) { + return true; + } + + // If the child is any_subtype_of(x) and the parent is not x, this only works if all known subclasses of x + // cast-compatible to the parent Deque worklist = new ArrayDeque(); - SootClass base = ((AnySubType) child).getBase().getSootClass(); + SootClass base = childBase.getSootClass(); if (base.isInterface()) { worklist.addAll(getAllImplementersOfInterface(base)); } else { From 47ad2a607a61222382b3eb4cf4c5f5a04d990839 Mon Sep 17 00:00:00 2001 From: Steven Arzt Date: Fri, 24 May 2024 17:02:50 +0200 Subject: [PATCH 2/4] added support for passing on types in the PAG in case we later want to later invoke a method that object which we have a summary. Example: List<...> l = ...; Spliterator<...> s = l.spliterator(); s.forEachRemaining(callback); We would normally lose the type information over the call to spliterator() if we don't have an implementation for that method. With the new deferred PAG summary, we can just use the declared type because we know that we will use a summary for the call to forEachRemaining() later anyway, so the actual type of the Spliterator is irrelevant. --- src/main/java/soot/jimple/spark/pag/PAG.java | 57 ++++-- .../callgraph/OnFlyCallGraphBuilder.java | 46 +++-- .../callgraph/VirtualEdgesSummaries.java | 164 +++++++++++++----- src/main/resources/virtualedges.xml | 18 ++ 4 files changed, 213 insertions(+), 72 deletions(-) diff --git a/src/main/java/soot/jimple/spark/pag/PAG.java b/src/main/java/soot/jimple/spark/pag/PAG.java index 5d2e0e36f00..9f0653c9c43 100644 --- a/src/main/java/soot/jimple/spark/pag/PAG.java +++ b/src/main/java/soot/jimple/spark/pag/PAG.java @@ -36,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import soot.AnySubType; import soot.Context; import soot.FastHierarchy; import soot.Kind; @@ -80,7 +81,9 @@ import soot.jimple.spark.solver.OnFlyCallGraph; import soot.jimple.toolkits.callgraph.Edge; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries; +import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.DeferredVirtualEdgeTarget; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.InstanceinvokeSource; +import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.InvocationVirtualEdgeTarget; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.VirtualEdge; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.VirtualEdgeSource; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.VirtualEdgeTarget; @@ -1093,19 +1096,47 @@ public void addCallTarget(Edge e) { if (edgeSrc instanceof InstanceinvokeSource) { for (VirtualEdgeTarget edgeTgt : ve.getTargets()) { - for (Local local : getOnFlyCallGraph().ofcgb().getReceiversOfVirtualEdge(edgeTgt, ie)) { - Node parm = srcmpag.nodeFactory().getNode(local); - parm = srcmpag.parameterize(parm, e.srcCtxt()); - parm = parm.getReplacement(); - - // Get the PAG node for the "this" local in the callback - Node thiz = tgtmpag.nodeFactory().caseThis(); - thiz = tgtmpag.parameterize(thiz, e.tgtCtxt()); - thiz = thiz.getReplacement(); - - // Make an edge from caller.argument to callee.this - addEdge(parm, thiz); - pval = addInterproceduralAssignment(parm, thiz, e); + if (edgeTgt instanceof InvocationVirtualEdgeTarget) { + for (Local local : getOnFlyCallGraph().ofcgb().getReceiversOfVirtualEdge((InvocationVirtualEdgeTarget) edgeTgt, + ie)) { + Node parm = srcmpag.nodeFactory().getNode(local); + parm = srcmpag.parameterize(parm, e.srcCtxt()); + parm = parm.getReplacement(); + + // Get the PAG node for the "this" local in the callback + Node thiz = tgtmpag.nodeFactory().caseThis(); + thiz = tgtmpag.parameterize(thiz, e.tgtCtxt()); + thiz = thiz.getReplacement(); + + // Make an edge from caller.argument to callee.this + addEdge(parm, thiz); + pval = addInterproceduralAssignment(parm, thiz, e); + } + } else if (edgeTgt instanceof DeferredVirtualEdgeTarget && e.srcStmt() instanceof AssignStmt + && ie.getMethodRef().getReturnType() instanceof RefType) { + DeferredVirtualEdgeTarget de = (DeferredVirtualEdgeTarget) edgeTgt; + + // We need to fake an edge to the return value of the call + Local lop = (Local) ((AssignStmt) e.srcStmt()).getLeftOp(); + Node ln = srcmpag.nodeFactory().getNode(lop); + ln = srcmpag.parameterize(ln, e.srcCtxt()); + ln = ln.getReplacement(); + + RefType rt = de.getTargetType(); + if (rt == null) + rt = (RefType) ie.getMethodRef().getReturnType(); + + // Fake an allocation node + AllocNode alloc + = makeAllocNode(new Pair((VarNode) ln, rt.getSootClass()), AnySubType.v(rt), e.src()); + + // temporary variable + VarNode tmp = makeLocalVarNode(alloc, rt, e.src()); + + // tmp = new T(); + addAllocEdge(alloc, tmp); + addEdge(tmp, ln); + // ofcg.updatedNode((VarNode) ln); } } } diff --git a/src/main/java/soot/jimple/toolkits/callgraph/OnFlyCallGraphBuilder.java b/src/main/java/soot/jimple/toolkits/callgraph/OnFlyCallGraphBuilder.java index a0f31fd957f..d333f483ed0 100644 --- a/src/main/java/soot/jimple/toolkits/callgraph/OnFlyCallGraphBuilder.java +++ b/src/main/java/soot/jimple/toolkits/callgraph/OnFlyCallGraphBuilder.java @@ -1,5 +1,7 @@ package soot.jimple.toolkits.callgraph; +import java.lang.reflect.Constructor; + /*- * #%L * Soot - a J*va Optimization Framework @@ -94,10 +96,13 @@ import soot.jimple.StringConstant; import soot.jimple.VirtualInvokeExpr; import soot.jimple.spark.pag.AllocDotField; +import soot.jimple.spark.pag.PAG; import soot.jimple.toolkits.annotation.nullcheck.NullnessAnalysis; import soot.jimple.toolkits.callgraph.ConstantArrayAnalysis.ArrayTypes; +import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.DeferredVirtualEdgeTarget; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.DirectTarget; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.IndirectTarget; +import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.InvocationVirtualEdgeTarget; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.VirtualEdge; import soot.jimple.toolkits.callgraph.VirtualEdgesSummaries.VirtualEdgeTarget; import soot.jimple.toolkits.reflection.ReflectionTraceInfo; @@ -438,7 +443,7 @@ private void resolveInvoke(Collection list) { Iterator mIt = getPublicMethodIterator(baseClass, reachingTypes, methodSizes, mustNotBeNull); while (mIt.hasNext()) { SootMethod sm = mIt.next(); - cm.addVirtualEdge(ics.container(), ics.stmt(), sm, Kind.REFL_INVOKE, null); + cm.addVirtualEdge(ics.getContainer(), ics.getStmt(), sm, Kind.REFL_INVOKE, null); } } } @@ -806,14 +811,20 @@ protected void findReceivers(SootMethod m, Body b) { InstanceInvokeExpr iie = (InstanceInvokeExpr) ie; Local receiver = (Local) iie.getBase(); MethodSubSignature subSig = new MethodSubSignature(iie.getMethodRef()); - addVirtualCallSite(s, m, receiver, iie, new MethodSubSignature(iie.getMethodRef()), Edge.ieToKind(iie)); VirtualEdge virtualEdge = virtualEdgeSummaries.getVirtualEdgesMatchingSubSig(subSig); if (virtualEdge != null) { for (VirtualEdgeTarget t : virtualEdge.targets) { - processVirtualEdgeSummary(m, s, receiver, t, virtualEdge.edgeType); + if (t instanceof InvocationVirtualEdgeTarget) { + processVirtualEdgeSummary(m, s, receiver, (InvocationVirtualEdgeTarget) t, virtualEdge.edgeType); + } else if (t instanceof DeferredVirtualEdgeTarget) { + addVirtualCallSite(s, m, receiver, iie, new MethodSubSignature(iie.getMethodRef()), Kind.GENERIC_FAKE); + } } } + + // if (!hasVirtualEdge || !iie.getMethod().isPhantom()) + addVirtualCallSite(s, m, receiver, iie, new MethodSubSignature(iie.getMethodRef()), Edge.ieToKind(iie)); } else if (ie instanceof DynamicInvokeExpr) { if (options.verbose()) { logger.warn("InvokeDynamic to " + ie + " not resolved during call-graph construction."); @@ -828,10 +839,10 @@ protected void findReceivers(SootMethod m, Body b) { for (VirtualEdgeTarget t : virtualEdge.targets) { if (t instanceof DirectTarget) { DirectTarget directTarget = (DirectTarget) t; - if (t.isBase()) { + if (directTarget.isBase()) { // this should not happen } else { - Value runnable = ie.getArg(t.argIndex); + Value runnable = ie.getArg(directTarget.argIndex); if (runnable instanceof Local) { addVirtualCallSite(s, m, (Local) runnable, null, directTarget.targetMethod, Kind.GENERIC_FAKE); } @@ -848,12 +859,12 @@ protected void findReceivers(SootMethod m, Body b) { } } - protected void processVirtualEdgeSummary(SootMethod m, final Stmt s, Local receiver, VirtualEdgeTarget target, + protected void processVirtualEdgeSummary(SootMethod m, final Stmt s, Local receiver, InvocationVirtualEdgeTarget target, Kind edgeType) { processVirtualEdgeSummary(m, s, s, receiver, target, edgeType); } - private Local getLocalForTarget(InvokeExpr ie, VirtualEdgeTarget target) { + private Local getLocalForTarget(InvokeExpr ie, InvocationVirtualEdgeTarget target) { if (target.isBase() && ie instanceof InstanceInvokeExpr) { return (Local) ((InstanceInvokeExpr) ie).getBase(); } @@ -870,7 +881,7 @@ private Local getLocalForTarget(InvokeExpr ie, VirtualEdgeTarget target) { } /** Returns all values that should be mapped to this in the edge target. **/ - public Set getReceiversOfVirtualEdge(VirtualEdgeTarget edgeTarget, InvokeExpr invokeExpr) { + public Set getReceiversOfVirtualEdge(InvocationVirtualEdgeTarget edgeTarget, InvokeExpr invokeExpr) { if (edgeTarget instanceof VirtualEdgesSummaries.IndirectTarget) { VirtualEdgesSummaries.IndirectTarget indirectTarget = (VirtualEdgesSummaries.IndirectTarget) edgeTarget; // Recursion case: We have an indirect target, which leads us to the statement where the local, @@ -892,11 +903,13 @@ public Set getReceiversOfVirtualEdge(VirtualEdgeTarget edgeTarget, Invoke for (VirtualCallSite site : sites) { if (methodName.equals(site.subSig())) { for (VirtualEdgeTarget subTargets : indirectTarget.getTargets()) { - // We have found the indirect target, recursively go down till we have a direct target, - // where we can get the local that finally gets converted to $this inside the callee. - results.addAll(getReceiversOfVirtualEdge(subTargets, site.iie())); - // We might have multiple calls of the same method on the receiver (e.g. if else) - // as well as multiple sub-targets, thus, we can't break here. + if (subTargets instanceof InvocationVirtualEdgeTarget) { + // We have found the indirect target, recursively go down till we have a direct target, + // where we can get the local that finally gets converted to $this inside the callee. + results.addAll(getReceiversOfVirtualEdge((InvocationVirtualEdgeTarget) subTargets, site.iie())); + // We might have multiple calls of the same method on the receiver (e.g. if else) + // as well as multiple sub-targets, thus, we can't break here. + } } } } @@ -911,7 +924,7 @@ public Set getReceiversOfVirtualEdge(VirtualEdgeTarget edgeTarget, Invoke } protected void processVirtualEdgeSummary(SootMethod callSiteMethod, Stmt callSite, final Stmt curStmt, Local receiver, - VirtualEdgeTarget target, Kind edgeType) { + InvocationVirtualEdgeTarget target, Kind edgeType) { // Get the target object referenced by this edge summary InvokeExpr ie = curStmt.getInvokeExpr(); Local targetLocal = getLocalForTarget(ie, target); @@ -941,8 +954,9 @@ protected void processVirtualEdgeSummary(SootMethod callSiteMethod, Stmt callSit if (w.getTargetMethod().equals(site.subSig())) { for (VirtualEdgeTarget siteTarget : w.getTargets()) { Stmt siteStmt = site.getStmt(); - if (siteStmt.containsInvokeExpr()) { - processVirtualEdgeSummary(callSiteMethod, callSite, siteStmt, receiver, siteTarget, edgeType); + if (siteStmt.containsInvokeExpr() && siteTarget instanceof InvocationVirtualEdgeTarget) { + processVirtualEdgeSummary(callSiteMethod, callSite, siteStmt, receiver, + (InvocationVirtualEdgeTarget) siteTarget, edgeType); } } } diff --git a/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java b/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java index 3e5d90da39b..1879d6050d8 100644 --- a/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java +++ b/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java @@ -38,6 +38,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.xml.parsers.DocumentBuilder; @@ -272,7 +273,8 @@ private static Element edgeTargetToXML(Document doc, VirtualEdgeTarget e) { for (VirtualEdgeTarget i : id.targets) { target.appendChild(edgeTargetToXML(doc, i)); } - + } else if (e instanceof DeferredVirtualEdgeTarget) { + target = doc.createElement("deferred"); } else { if (e == null) { throw new IllegalArgumentException("Unsupported null edge type"); @@ -285,13 +287,17 @@ private static Element edgeTargetToXML(Document doc, VirtualEdgeTarget e) { target.setAttribute("declaringclass", e.targetType.getClassName()); } - target.setAttribute("subsignature", e.targetMethod.toString()); - if (e.isBase()) { - target.setAttribute("target-position", "base"); - } else { - target.setAttribute("index", String.valueOf(e.argIndex)); - target.setAttribute("target-position", "argument"); + if (e instanceof InvocationVirtualEdgeTarget) { + InvocationVirtualEdgeTarget it = (InvocationVirtualEdgeTarget) e; + target.setAttribute("subsignature", it.targetMethod.toString()); + if (it.isBase()) { + target.setAttribute("target-position", "base"); + } else { + target.setAttribute("index", String.valueOf(it.argIndex)); + target.setAttribute("target-position", "argument"); + } } + return target; } @@ -331,6 +337,7 @@ private static List parseEdgeTargets(Element targetsElement) for (int i = 0, e = children.getLength(); i < e; i++) { if (children.item(i).getNodeType() == Node.ELEMENT_NODE) { Element targetElement = (Element) children.item(i); + RefType type = getDeclaringClassType(targetElement); switch (targetElement.getTagName()) { case "direct": { @@ -338,7 +345,6 @@ private static List parseEdgeTargets(Element targetsElement) = new MethodSubSignature(nmbr.findOrAdd(targetElement.getAttribute("subsignature"))); String tpos = targetElement.getAttribute("target-position"); - RefType type = getDeclaringClassType(targetElement); DirectTarget dt; switch (tpos) { case "argument": @@ -371,14 +377,13 @@ private static List parseEdgeTargets(Element targetsElement) MethodSubSignature subsignature = new MethodSubSignature(nmbr.findOrAdd(targetElement.getAttribute("subsignature"))); String tpos = targetElement.getAttribute("target-position"); - RefType dClass = getDeclaringClassType(targetElement); switch (tpos) { case "argument": int argIdx = Integer.valueOf(targetElement.getAttribute("index")); - target = new IndirectTarget(dClass, subsignature, argIdx); + target = new IndirectTarget(type, subsignature, argIdx); break; case "base": - target = new IndirectTarget(dClass, subsignature); + target = new IndirectTarget(type, subsignature); break; default: throw new IllegalArgumentException("Unsupported target position " + tpos); @@ -394,6 +399,11 @@ private static List parseEdgeTargets(Element targetsElement) targets.add(target); break; } + case "deferred": { + DeferredVirtualEdgeTarget target = new DeferredVirtualEdgeTarget(type); + targets.add(target); + break; + } } } } @@ -555,25 +565,23 @@ public boolean equals(Object obj) { } + /** + * Abstract base class for all virtual edge targets. + * + * @author Steven Arzt + * + */ public static abstract class VirtualEdgeTarget { - protected int argIndex; - protected MethodSubSignature targetMethod; protected RefType targetType; VirtualEdgeTarget() { // internal use only } - public VirtualEdgeTarget(RefType targetType, MethodSubSignature targetMethod) { - this.argIndex = BASE_INDEX; - this.targetMethod = targetMethod; - this.targetType = targetType; - } + public abstract VirtualEdgeTarget clone(); - public VirtualEdgeTarget(RefType targetType, MethodSubSignature targetMethod, int argIndex) { - this.argIndex = argIndex; - this.targetMethod = targetMethod; + public VirtualEdgeTarget(RefType targetType) { this.targetType = targetType; } @@ -581,6 +589,89 @@ public RefType getTargetType() { return targetType; } + @Override + public int hashCode() { + return Objects.hash(targetType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VirtualEdgeTarget other = (VirtualEdgeTarget) obj; + return Objects.equals(targetType, other.targetType); + } + + } + + /** + *

+ * A deferred edge target models cases in which a call does not immediately invoke the callback, but instead returns an + * object on which a callback an be invoked later. + *

+ * + *
+   * List l = ...
+   * Spliterator split = l.spliterator();
+   * split.forEachRemaining(callback);
+   * 
+ * + * @author arzt + * + */ + public static class DeferredVirtualEdgeTarget extends VirtualEdgeTarget { + + DeferredVirtualEdgeTarget() { + // internal use only + } + + public DeferredVirtualEdgeTarget(RefType targetType) { + super(targetType); + } + + @Override + public DeferredVirtualEdgeTarget clone() { + return new DeferredVirtualEdgeTarget(targetType); + } + + } + + /** + *

+ * The target of a PAG or callgraph edge that corresponds to the immediate execution of a method. + *

+ * + *

+ * The method can either be specified directly, or indirectly by following a chain obf subsequent calls, which is modeled + * by the respective derived classes of this abstract base class. + *

+ * + */ + public static abstract class InvocationVirtualEdgeTarget extends VirtualEdgeTarget { + + protected int argIndex; + protected MethodSubSignature targetMethod; + + InvocationVirtualEdgeTarget() { + // internal use only + } + + public InvocationVirtualEdgeTarget(RefType targetType, MethodSubSignature targetMethod) { + super(targetType); + this.argIndex = BASE_INDEX; + this.targetMethod = targetMethod; + } + + public InvocationVirtualEdgeTarget(RefType targetType, MethodSubSignature targetMethod, int argIndex) { + super(targetType); + this.argIndex = argIndex; + this.targetMethod = targetMethod; + } + @Override public String toString() { return isBase() ? "base" : String.format("argument %d", argIndex); @@ -598,8 +689,6 @@ public void setArgIndex(int value) { this.argIndex = value; } - public abstract VirtualEdgeTarget clone(); - /** * Clones the edge, but with a potentially different arg index * @@ -616,36 +705,25 @@ public MethodSubSignature getTargetMethod() { @Override public int hashCode() { final int prime = 31; - int result = 1; - result = prime * result + argIndex; - result = prime * result + ((targetMethod == null) ? 0 : targetMethod.hashCode()); + int result = super.hashCode(); + result = prime * result + Objects.hash(argIndex, targetMethod); return result; } @Override public boolean equals(Object obj) { - if (this == obj) { + if (this == obj) return true; - } - if ((obj == null) || (getClass() != obj.getClass())) { - return false; - } - VirtualEdgeTarget other = (VirtualEdgeTarget) obj; - if (argIndex != other.argIndex) { + if (!super.equals(obj)) return false; - } - if (targetMethod == null) { - if (other.targetMethod != null) { - return false; - } - } else if (!targetMethod.equals(other.targetMethod)) { + if (getClass() != obj.getClass()) return false; - } - return true; + InvocationVirtualEdgeTarget other = (InvocationVirtualEdgeTarget) obj; + return argIndex == other.argIndex && Objects.equals(targetMethod, other.targetMethod); } } - public static class DirectTarget extends VirtualEdgeTarget { + public static class DirectTarget extends InvocationVirtualEdgeTarget { private List parameterMappings = new ArrayList<>(); DirectTarget() { @@ -758,7 +836,7 @@ private static Value getValueByIndex(InvokeExpr expr, int idx) { return expr.getArg(idx); } - public static class IndirectTarget extends VirtualEdgeTarget { + public static class IndirectTarget extends InvocationVirtualEdgeTarget { List targets = new ArrayList<>(); IndirectTarget() { diff --git a/src/main/resources/virtualedges.xml b/src/main/resources/virtualedges.xml index 596531779c8..c906266f0d1 100644 --- a/src/main/resources/virtualedges.xml +++ b/src/main/resources/virtualedges.xml @@ -719,4 +719,22 @@ target-position="argument" index="0" /> + + + + + + + + + + + + + + From 249f856972caa826462e08f11cd636a214a91dfe Mon Sep 17 00:00:00 2001 From: Steven Arzt Date: Fri, 24 May 2024 17:18:37 +0200 Subject: [PATCH 3/4] fixed style violations --- src/main/java/soot/jimple/spark/pag/PAG.java | 3 ++- .../callgraph/VirtualEdgesSummaries.java | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/soot/jimple/spark/pag/PAG.java b/src/main/java/soot/jimple/spark/pag/PAG.java index 9f0653c9c43..6a6ed14b94f 100644 --- a/src/main/java/soot/jimple/spark/pag/PAG.java +++ b/src/main/java/soot/jimple/spark/pag/PAG.java @@ -1123,8 +1123,9 @@ public void addCallTarget(Edge e) { ln = ln.getReplacement(); RefType rt = de.getTargetType(); - if (rt == null) + if (rt == null) { rt = (RefType) ie.getMethodRef().getReturnType(); + } // Fake an allocation node AllocNode alloc diff --git a/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java b/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java index 1879d6050d8..5739d8ad219 100644 --- a/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java +++ b/src/main/java/soot/jimple/toolkits/callgraph/VirtualEdgesSummaries.java @@ -596,12 +596,15 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } VirtualEdgeTarget other = (VirtualEdgeTarget) obj; return Objects.equals(targetType, other.targetType); } @@ -712,12 +715,15 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (!super.equals(obj)) + } + if (!super.equals(obj)) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } InvocationVirtualEdgeTarget other = (InvocationVirtualEdgeTarget) obj; return argIndex == other.argIndex && Objects.equals(targetMethod, other.targetMethod); } From 581a6e7bff94f9ce60cf137e5f4c86b78d95fea5 Mon Sep 17 00:00:00 2001 From: Jan Peter Stotz Date: Thu, 23 May 2024 15:26:10 +0200 Subject: [PATCH 4/4] Split handling of fill-array-data instructions into two phases: first the command transforming to jimple instructions and later a second phase in DexFillArrayDataTransformer that recovers the array data types and allies it to the values. --- src/main/java/soot/dexpler/DexBody.java | 2 +- .../dexpler/DexFillArrayDataTransformer.java | 138 ++++++++++++++++++ .../FillArrayDataInstruction.java | 119 ++++----------- .../typing/UntypedIntOrFloatConstant.java | 9 ++ 4 files changed, 174 insertions(+), 94 deletions(-) create mode 100644 src/main/java/soot/dexpler/DexFillArrayDataTransformer.java diff --git a/src/main/java/soot/dexpler/DexBody.java b/src/main/java/soot/dexpler/DexBody.java index 4978f8de665..31fb8036a6e 100755 --- a/src/main/java/soot/dexpler/DexBody.java +++ b/src/main/java/soot/dexpler/DexBody.java @@ -40,7 +40,6 @@ import java.util.List; import java.util.Set; import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicReference; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPathResolver; @@ -771,6 +770,7 @@ public Body jimplify(Body b, SootMethod m) { DeadAssignmentEliminator.v().transform(jBody); UnconditionalBranchFolder.v().transform(jBody); } + DexFillArrayDataTransformer.v().transform(jBody); TypeAssigner.v().transform(jBody); diff --git a/src/main/java/soot/dexpler/DexFillArrayDataTransformer.java b/src/main/java/soot/dexpler/DexFillArrayDataTransformer.java new file mode 100644 index 00000000000..ffe64321f9e --- /dev/null +++ b/src/main/java/soot/dexpler/DexFillArrayDataTransformer.java @@ -0,0 +1,138 @@ +package soot.dexpler; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2012 Michael Markert, Frank Hartmann + * + * (c) 2012 University of Luxembourg - Interdisciplinary Centre for + * Security Reliability and Trust (SnT) - All rights reserved + * Alexandre Bartel + * + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import soot.ArrayType; +import soot.Body; +import soot.BodyTransformer; +import soot.G; +import soot.Local; +import soot.Type; +import soot.Unit; +import soot.Value; +import soot.dexpler.instructions.FillArrayDataInstruction; +import soot.dexpler.typing.UntypedConstant; +import soot.jimple.ArrayRef; +import soot.jimple.AssignStmt; +import soot.jimple.InvokeExpr; +import soot.jimple.NewArrayExpr; +import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.ExceptionalUnitGraphFactory; +import soot.toolkits.scalar.LocalDefs; + +/** + * If Dalvik bytecode can contain fill-array-data instructions that can fill an array with data elements we only + * know the element size of. + * + * Therefore when processing such instructions in {@link FillArrayDataInstruction} we don't know the exact type of the data + * that is loaded. Because of (conditional) branches in the code, identifying the type is not always possible at that stage. + * Instead {@link UntypedConstant} constants are used. These constants are processed by this transformer and get their final + * type. + * + * + * @author Jan Peter Stotz + * + */ +public class DexFillArrayDataTransformer extends BodyTransformer { + private static final Logger logger = LoggerFactory.getLogger(DexFillArrayDataTransformer.class); + + public static DexFillArrayDataTransformer v() { + return new DexFillArrayDataTransformer(); + } + + protected void internalTransform(final Body body, String phaseName, Map options) { + final ExceptionalUnitGraph g = ExceptionalUnitGraphFactory.createExceptionalUnitGraph(body, DalvikThrowAnalysis.v()); + final LocalDefs defs = G.v().soot_toolkits_scalar_LocalDefsFactory().newLocalDefs(g); + + for (Iterator unitIt = body.getUnits().snapshotIterator(); unitIt.hasNext();) { + Unit u = unitIt.next(); + if (!(u instanceof AssignStmt)) { + continue; + } + AssignStmt ass = (AssignStmt) u; + Value rightOp = ass.getRightOp(); + if (rightOp instanceof UntypedConstant) { + Value left = ass.getLeftOp(); + if (left instanceof ArrayRef) { + ArrayRef leftArray = (ArrayRef) left; + + Local l = (Local) leftArray.getBase(); + List assDefs = defs.getDefsOfAt(l, ass); + List arrayTypes = new LinkedList<>(); + for (Unit d : assDefs) { + if (d instanceof AssignStmt) { + AssignStmt arrayAssign = (AssignStmt) d; + Value source = arrayAssign.getRightOp(); + if (source instanceof NewArrayExpr) { + NewArrayExpr newArray = (NewArrayExpr) source; + arrayTypes.add(newArray.getBaseType()); + continue; + } + if (source instanceof InvokeExpr) { + InvokeExpr invExpr = (InvokeExpr) source; + Type aType = invExpr.getMethodRef().getReturnType(); + if (!(aType instanceof ArrayType)) { + throw new InternalError("Failed to identify the array type. The identified method invocation " + + "does not return an array type. Invocation: " + invExpr.getMethodRef()); + } + arrayTypes.add(((ArrayType) aType).getArrayElementType()); + continue; + } + throw new InternalError("Unsupported array definition statement: " + d); + } + } + if (arrayTypes.isEmpty()) { + throw new InternalError("Failed to determine the array type "); + } + if (arrayTypes.size() > 1) { + arrayTypes = arrayTypes.stream().distinct().collect(Collectors.toList()); + if (arrayTypes.size() > 1) { + logger.warn("Found multiple possible array types, using first ignoreing the others: {}", arrayTypes); + } + } + + // We found the array type, now convert the untyped constant value to it's final type + Type elementType = arrayTypes.get(0); + Value constant = ass.getRightOp(); + UntypedConstant untyped = (UntypedConstant) constant; + ass.setRightOp(untyped.defineType(elementType)); + } + } + } + } + +} diff --git a/src/main/java/soot/dexpler/instructions/FillArrayDataInstruction.java b/src/main/java/soot/dexpler/instructions/FillArrayDataInstruction.java index 74ab9e5f974..42f0faa0c4a 100644 --- a/src/main/java/soot/dexpler/instructions/FillArrayDataInstruction.java +++ b/src/main/java/soot/dexpler/instructions/FillArrayDataInstruction.java @@ -27,41 +27,35 @@ * #L% */ -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.formats.ArrayPayload; -import org.jf.dexlib2.iface.instruction.formats.Instruction22c; import org.jf.dexlib2.iface.instruction.formats.Instruction31t; -import org.jf.dexlib2.iface.reference.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import soot.ArrayType; -import soot.BooleanType; -import soot.ByteType; -import soot.CharType; -import soot.DoubleType; -import soot.FloatType; -import soot.IntType; import soot.Local; -import soot.LongType; -import soot.ShortType; -import soot.Type; import soot.dexpler.DexBody; -import soot.dexpler.DexType; +import soot.dexpler.DexFillArrayDataTransformer; +import soot.dexpler.typing.UntypedConstant; +import soot.dexpler.typing.UntypedIntOrFloatConstant; +import soot.dexpler.typing.UntypedLongOrDoubleConstant; import soot.jimple.ArrayRef; import soot.jimple.AssignStmt; -import soot.jimple.DoubleConstant; -import soot.jimple.FloatConstant; +import soot.jimple.Constant; import soot.jimple.IntConstant; import soot.jimple.Jimple; -import soot.jimple.LongConstant; -import soot.jimple.NumericConstant; import soot.jimple.Stmt; +/** + * Converts fill-array-data instructions and associated data blocks into a series of assignment instructions + * (one for each array index the data block contains a value). + * + * As the data block contains untyped data, only the number of bytes per element is known. Recovering the array type at the + * stage this class is used on would require a detailed analysis on the dex code. Therefore we save the data elements as + * {@link UntypedConstant} and later use {@link DexFillArrayDataTransformer} to convert the values to their final type. + */ public class FillArrayDataInstruction extends PseudoInstruction { private static final Logger logger = LoggerFactory.getLogger(FillArrayDataInstruction.class); @@ -95,13 +89,11 @@ public void jimplify(DexBody body) { List elements = arrayTable.getArrayElements(); int numElements = elements.size(); + int elementsWidth = arrayTable.getElementWidth(); Stmt firstAssign = null; for (int i = 0; i < numElements; i++) { ArrayRef arrayRef = Jimple.v().newArrayRef(arrayReference, IntConstant.v(i)); - NumericConstant element = getArrayElement(elements.get(i), body, destRegister); - if (element == null) { - break; - } + Constant element = getArrayElement(elements.get(i), elementsWidth); AssignStmt assign = Jimple.v().newAssignStmt(arrayRef, element); addTags(assign); body.add(assign); @@ -110,6 +102,8 @@ public void jimplify(DexBody body) { } } if (firstAssign == null) { // if numElements == 0. Is it possible? + logger.warn("No assign statements created for array at address 0x{} - empty array data section?", + Integer.toHexString(targetAddress)); firstAssign = Jimple.v().newNopStmt(); body.add(firstAssign); } @@ -122,80 +116,19 @@ public void jimplify(DexBody body) { } - private NumericConstant getArrayElement(Number element, DexBody body, int arrayRegister) { - - List instructions = body.instructionsBefore(this); - Set usedRegisters = new HashSet(); - usedRegisters.add(arrayRegister); - - Type elementType = null; - Outer: for (DexlibAbstractInstruction i : instructions) { - if (usedRegisters.isEmpty()) { - break; - } - - for (int reg : usedRegisters) { - if (i instanceof NewArrayInstruction) { - NewArrayInstruction newArrayInstruction = (NewArrayInstruction) i; - Instruction22c instruction22c = (Instruction22c) newArrayInstruction.instruction; - if (instruction22c.getRegisterA() == reg) { - ArrayType arrayType = (ArrayType) DexType.toSoot((TypeReference) instruction22c.getReference()); - elementType = arrayType.getElementType(); - break Outer; - } - } - } - - // // look for obsolete registers - // for (int reg : usedRegisters) { - // if (i.overridesRegister(reg)) { - // usedRegisters.remove(reg); - // break; // there can't be more than one obsolete - // } - // } - - // look for new registers - for (int reg : usedRegisters) { - int newRegister = i.movesToRegister(reg); - if (newRegister != -1) { - usedRegisters.add(newRegister); - usedRegisters.remove(reg); - break; // there can't be more than one new - } - } + private Constant getArrayElement(Number element, int elementsWidth) { + if (elementsWidth == 2) { + // For size = 2 the only possible array type is short[] + return IntConstant.v(element.shortValue()); } - if (elementType == null) { - // throw new InternalError("Unable to find array type to type array elements!"); - logger.warn("Unable to find array type to type array elements! Array was not defined! (obfuscated bytecode?)"); - return null; - } - - NumericConstant value; - - if (elementType instanceof BooleanType) { - value = IntConstant.v(element.intValue()); - IntConstant ic = (IntConstant) value; - if (ic.value != 0) { - value = IntConstant.v(1); - } - } else if (elementType instanceof ByteType) { - value = IntConstant.v(element.byteValue()); - } else if (elementType instanceof CharType || elementType instanceof ShortType) { - value = IntConstant.v(element.shortValue()); - } else if (elementType instanceof DoubleType) { - value = DoubleConstant.v(Double.longBitsToDouble(element.longValue())); - } else if (elementType instanceof FloatType) { - value = FloatConstant.v(Float.intBitsToFloat(element.intValue())); - } else if (elementType instanceof IntType) { - value = IntConstant.v(element.intValue()); - } else if (elementType instanceof LongType) { - value = LongConstant.v(element.longValue()); - } else { - throw new RuntimeException("Invalid Array Type occured in FillArrayDataInstruction: " + elementType); + if (elementsWidth <= 4) { + // can be array of int, char, boolean, float + return UntypedIntOrFloatConstant.v(element.intValue()); } - return value; + // can be array of long or double + return UntypedLongOrDoubleConstant.v(element.longValue()); } @Override diff --git a/src/main/java/soot/dexpler/typing/UntypedIntOrFloatConstant.java b/src/main/java/soot/dexpler/typing/UntypedIntOrFloatConstant.java index 99b65c415db..7611cffdd25 100644 --- a/src/main/java/soot/dexpler/typing/UntypedIntOrFloatConstant.java +++ b/src/main/java/soot/dexpler/typing/UntypedIntOrFloatConstant.java @@ -70,6 +70,13 @@ public IntConstant toIntConstant() { return IntConstant.v(value); } + public IntConstant toBooleanConstant() { + if (value != 0) { + return IntConstant.v(1); + } + return IntConstant.v(value); + } + @Override public Value defineType(Type t) { if (t instanceof FloatType) { @@ -77,6 +84,8 @@ public Value defineType(Type t) { } else if (t instanceof IntType || t instanceof CharType || t instanceof BooleanType || t instanceof ByteType || t instanceof ShortType) { return this.toIntConstant(); + } else if (t instanceof BooleanType) { + return toBooleanConstant(); } else { if (value == 0 && t instanceof RefLikeType) { return NullConstant.v();