diff --git a/docs/en/pointer-analysis-framework.adoc b/docs/en/pointer-analysis-framework.adoc index d39418e7b..156764078 100644 --- a/docs/en/pointer-analysis-framework.adoc +++ b/docs/en/pointer-analysis-framework.adoc @@ -87,6 +87,10 @@ See <> for more details. ** Default value: `false` ** Specify whether to dump points-to results in YAML format. This option is independent of `dump` and `dump-ci` output formats. +* Only dump points-to results of application code: `only-dump-app:[true|false]` +** Default value: `false` +** When set to `true`, `dump-yaml`, `dump-ci` will only dump analysis results of application code (and ignores library code). + * Time limit: `time-limit:` ** Default value: `-1` ** Specify a time limit for pointer analysis (unit: second).When it is `-1`, there is no time limit. diff --git a/src/main/java/pascal/taie/AbstractWorldBuilder.java b/src/main/java/pascal/taie/AbstractWorldBuilder.java index bb9b477c1..56925866c 100644 --- a/src/main/java/pascal/taie/AbstractWorldBuilder.java +++ b/src/main/java/pascal/taie/AbstractWorldBuilder.java @@ -49,8 +49,6 @@ public abstract class AbstractWorldBuilder implements WorldBuilder { private static final Logger logger = LogManager.getLogger(AbstractWorldBuilder.class); - protected static final String JREs = "java-benchmarks/JREs"; - protected static final List implicitEntries = List.of( "", "(java.lang.ThreadGroup,java.lang.Runnable)>", @@ -75,7 +73,7 @@ protected static String getClassPath(Options options) { .collect(Collectors.joining(File.pathSeparator)); } else { // when prependJVM is not set, we manually specify JRE jars // check existence of JREs - File jreDir = new File(JREs); + File jreDir = new File(options.getLibJREPath()); if (!jreDir.exists()) { throw new RuntimeException(""" Failed to locate Java library. @@ -85,7 +83,7 @@ protected static String getClassPath(Options options) { then put it in Tai-e's working directory."""); } String jrePath = String.format("%s/jre1.%d", - JREs, options.getJavaVersion()); + options.getLibJREPath(), options.getJavaVersion()); try (Stream paths = Files.walk(Path.of(jrePath))) { return Streams.concat( paths.map(Path::toString).filter(p -> p.endsWith(".jar")), diff --git a/src/main/java/pascal/taie/analysis/graph/flowgraph/FlowKind.java b/src/main/java/pascal/taie/analysis/graph/flowgraph/FlowKind.java index 42ffd8e88..6d976b7fd 100644 --- a/src/main/java/pascal/taie/analysis/graph/flowgraph/FlowKind.java +++ b/src/main/java/pascal/taie/analysis/graph/flowgraph/FlowKind.java @@ -39,5 +39,17 @@ public enum FlowKind { PARAMETER_PASSING, RETURN, + // cut-shortcut + ARG_TO_HOST, // container entrance method, for example, v.add(e), o \in pts(v), e -> host(o) + HOST_TO_RESULT, + SUBSET, + CORRELATION, + ARRAYCOPY, + ID, + VIRTUAL_ARRAY, + VIRTUAL_ARG, + SET, GET, + NON_RELAY_GET, // relay edge + OTHER, } diff --git a/src/main/java/pascal/taie/analysis/pta/PointerAnalysis.java b/src/main/java/pascal/taie/analysis/pta/PointerAnalysis.java index 7b293b951..e47b3576c 100644 --- a/src/main/java/pascal/taie/analysis/pta/PointerAnalysis.java +++ b/src/main/java/pascal/taie/analysis/pta/PointerAnalysis.java @@ -30,6 +30,7 @@ import pascal.taie.analysis.pta.core.cs.selector.ContextSelectorFactory; import pascal.taie.analysis.pta.core.heap.AllocationSiteBasedModel; import pascal.taie.analysis.pta.core.heap.HeapModel; +import pascal.taie.analysis.pta.core.solver.CutShortcutSolver; import pascal.taie.analysis.pta.core.solver.DefaultSolver; import pascal.taie.analysis.pta.core.solver.Solver; import pascal.taie.analysis.pta.plugin.AnalysisTimer; @@ -40,6 +41,10 @@ import pascal.taie.analysis.pta.plugin.ReferenceHandler; import pascal.taie.analysis.pta.plugin.ResultProcessor; import pascal.taie.analysis.pta.plugin.ThreadHandler; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.ContainerAccessHandler; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.MakeDefaultContainerConfig; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.FieldAccessHandler; +import pascal.taie.analysis.pta.plugin.cutshortcut.localflow.LocalFlowHandler; import pascal.taie.analysis.pta.plugin.exception.ExceptionAnalysis; import pascal.taie.analysis.pta.plugin.invokedynamic.InvokeDynamicAnalysis; import pascal.taie.analysis.pta.plugin.invokedynamic.Java9StringConcatHandler; @@ -75,52 +80,59 @@ public PointerAnalysisResult analyze() { HeapModel heapModel = new AllocationSiteBasedModel(options); ContextSelector selector = null; String advanced = options.getString("advanced"); + String solverType = options.has("solver") ? options.getString("solver"): "default"; // which solver to use String cs = options.getString("cs"); if (advanced != null) { - if (advanced.equals("collection")) { + if (advanced.equals("collection")) selector = ContextSelectorFactory.makeSelectiveSelector(cs, new CollectionMethods(World.get().getClassHierarchy()).get()); - } else { + else { // run context-insensitive analysis as pre-analysis - PointerAnalysisResult preResult = runAnalysis(heapModel, - ContextSelectorFactory.makeCISelector()); - if (advanced.startsWith("scaler")) { + PointerAnalysisResult preResult = runAnalysis(heapModel, ContextSelectorFactory.makeCISelector(), solverType); + if (advanced.startsWith("scaler")) selector = Monitor.runAndCount(() -> ContextSelectorFactory .makeGuidedSelector(Scaler.run(preResult, advanced)), "Scaler", Level.INFO); - } else if (advanced.startsWith("zipper")) { + else if (advanced.startsWith("zipper")) selector = Monitor.runAndCount(() -> ContextSelectorFactory .makeSelectiveSelector(cs, Zipper.run(preResult, advanced)), "Zipper", Level.INFO); - } else if (advanced.equals("mahjong")) { + else if (advanced.equals("mahjong")) heapModel = Monitor.runAndCount(() -> Mahjong.run(preResult, options), "Mahjong", Level.INFO); - } else { + else throw new IllegalArgumentException( "Illegal advanced analysis argument: " + advanced); - } + } } if (selector == null) { selector = ContextSelectorFactory.makePlainSelector(cs); } - return runAnalysis(heapModel, selector); + return runAnalysis(heapModel, selector, solverType); } private PointerAnalysisResult runAnalysis(HeapModel heapModel, - ContextSelector selector) { + ContextSelector selector, + String solverType) { AnalysisOptions options = getOptions(); - Solver solver = new DefaultSolver(options, - heapModel, selector, new MapBasedCSManager()); + Solver solver; + if (solverType.equals("default")) + solver = new DefaultSolver(options, heapModel, selector, new MapBasedCSManager()); + // cut-shortcut + else if (solverType.equals("csc")) + solver = new CutShortcutSolver(options, heapModel, selector, new MapBasedCSManager()); + else + throw new IllegalArgumentException("Illegal solver type: " + solverType); // The initialization of some Plugins may read the fields in solver, // e.g., contextSelector or csManager, thus we initialize Plugins - // after setting all other fields of solver. - setPlugin(solver, options); + // after setting all other fields of solver.setPlugin(solver, options); + setPlugin(solver, options, solverType); solver.solve(); return solver.getResult(); } - private static void setPlugin(Solver solver, AnalysisOptions options) { + private static void setPlugin(Solver solver, AnalysisOptions options, String solverType) { CompositePlugin plugin = new CompositePlugin(); // add builtin plugins // To record elapsed time precisely, AnalysisTimer should be added at first. @@ -132,6 +144,15 @@ private static void setPlugin(Solver solver, AnalysisOptions options) { new NativeModeller(), new ExceptionAnalysis() ); + if (solverType.equals("csc")) { + MakeDefaultContainerConfig.make(); + plugin.addPlugin( + new LocalFlowHandler(), + new FieldAccessHandler(), + new ContainerAccessHandler() + ); + } + int javaVersion = World.get().getOptions().getJavaVersion(); if (javaVersion < 9) { // current reference handler doesn't support Java 9+ diff --git a/src/main/java/pascal/taie/analysis/pta/core/cs/element/AbstractPointer.java b/src/main/java/pascal/taie/analysis/pta/core/cs/element/AbstractPointer.java index 3b16185e5..ec2c50201 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/cs/element/AbstractPointer.java +++ b/src/main/java/pascal/taie/analysis/pta/core/cs/element/AbstractPointer.java @@ -45,6 +45,8 @@ abstract class AbstractPointer implements Pointer { private final ArrayList outEdges = new ArrayList<>(4); + private final ArrayList inEdges = new ArrayList<>(4); + private Set> filters = Set.of(); protected AbstractPointer(int index) { @@ -95,6 +97,7 @@ public PointerFlowEdge addEdge(PointerFlowEdge edge) { assert edge.source() == this; if (successors.add(edge.target())) { outEdges.add(edge); + edge.target().addInEdge(edge); return edge; } else if (edge.kind() == FlowKind.OTHER) { for (PointerFlowEdge outEdge : outEdges) { @@ -103,11 +106,17 @@ public PointerFlowEdge addEdge(PointerFlowEdge edge) { } } outEdges.add(edge); + edge.target().addInEdge(edge); return edge; } return null; } + public void addInEdge(PointerFlowEdge edge) { + assert edge.target() == this; + inEdges.add(edge); + } + @Override public void removeEdgesIf(Predicate filter) { outEdges.removeIf(filter); @@ -118,6 +127,11 @@ public Set getOutEdges() { return Collections.unmodifiableSet(new ArraySet<>(outEdges, true)); } + @Override + public Set getInEdges() { + return Collections.unmodifiableSet(new ArraySet<>(inEdges, true)); + } + @Override public int getOutDegree() { return outEdges.size(); diff --git a/src/main/java/pascal/taie/analysis/pta/core/cs/element/CSManager.java b/src/main/java/pascal/taie/analysis/pta/core/cs/element/CSManager.java index e02141ced..9c1e055d4 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/cs/element/CSManager.java +++ b/src/main/java/pascal/taie/analysis/pta/core/cs/element/CSManager.java @@ -24,6 +24,7 @@ import pascal.taie.analysis.pta.core.cs.context.Context; import pascal.taie.analysis.pta.core.heap.Obj; + import pascal.taie.ir.exp.Var; import pascal.taie.ir.stmt.Invoke; import pascal.taie.language.classes.JField; diff --git a/src/main/java/pascal/taie/analysis/pta/core/cs/element/Pointer.java b/src/main/java/pascal/taie/analysis/pta/core/cs/element/Pointer.java index 4b0275149..cff3c7586 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/cs/element/Pointer.java +++ b/src/main/java/pascal/taie/analysis/pta/core/cs/element/Pointer.java @@ -97,6 +97,8 @@ public interface Pointer extends Indexable { */ PointerFlowEdge addEdge(PointerFlowEdge edge); + void addInEdge(PointerFlowEdge edge); + /** * Removes out edges of this pointer if they satisfy the filter. *

@@ -112,6 +114,11 @@ public interface Pointer extends Indexable { */ Set getOutEdges(); + /** + * @return out edges of this pointer in pointer flow graph. + */ + Set getInEdges(); + /** * @return out degree of this pointer in pointer flow graph. */ diff --git a/src/main/java/pascal/taie/analysis/pta/core/solver/CutShortcutSolver.java b/src/main/java/pascal/taie/analysis/pta/core/solver/CutShortcutSolver.java new file mode 100644 index 000000000..366b52321 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/core/solver/CutShortcutSolver.java @@ -0,0 +1,323 @@ +package pascal.taie.analysis.pta.core.solver; + +import pascal.taie.analysis.graph.callgraph.CallKind; +import pascal.taie.analysis.graph.callgraph.Edge; +import pascal.taie.analysis.graph.flowgraph.FlowKind; +import pascal.taie.analysis.pta.core.cs.context.Context; +import pascal.taie.analysis.pta.core.cs.element.*; +import pascal.taie.analysis.pta.core.cs.selector.ContextSelector; +import pascal.taie.analysis.pta.core.heap.HeapModel; +import pascal.taie.analysis.pta.plugin.CompositePlugin; +import pascal.taie.analysis.pta.plugin.Plugin; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.ContainerAccessHandler; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.ContainerConfig; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.HostKind; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.*; +import pascal.taie.analysis.pta.plugin.reflection.ReflectiveCallEdge; +import pascal.taie.analysis.pta.pts.PointsToSet; +import pascal.taie.config.AnalysisOptions; +import pascal.taie.ir.exp.Exp; +import pascal.taie.ir.exp.InvokeExp; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.Invoke; +import pascal.taie.ir.stmt.LoadField; +import pascal.taie.ir.stmt.StoreField; +import pascal.taie.language.classes.JClass; +import pascal.taie.language.classes.JField; +import pascal.taie.language.classes.JMethod; +import pascal.taie.language.type.NullType; +import pascal.taie.language.type.ReferenceType; +import pascal.taie.language.type.Type; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.MultiMap; +import pascal.taie.util.collection.Sets; +import pascal.taie.util.collection.TwoKeyMap; + +import java.util.Set; + +import static pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty.setVirtualArg; +import static pascal.taie.analysis.pta.plugin.cutshortcut.SpecialVariables.isNonRelay; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.ClassAndTypeClassifier.isHashtableType; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.ClassAndTypeClassifier.isVectorType; + +public class CutShortcutSolver extends DefaultSolver { + private ContainerConfig containerConfig; + + private FieldAccessHandler fieldAccessHandler = null; + + private ContainerAccessHandler containerAccessHandler = null; + + // cutRetuns resolved By LocalFlowHandler + private final Set cutReturnVars = Sets.newSet(); + + private final Set cutStoreFields = Sets.newSet(); + + private final Set selectedMethods = Sets.newSet(); + + // callsites whose callee should not be cut, handle container access whose object type is not modeled + private final Set recoveredCallSites = Sets.newSet(); + + public CutShortcutSolver(AnalysisOptions options, HeapModel heapModel, + ContextSelector contextSelector, CSManager csManager) { + super(options, heapModel, contextSelector, csManager); + } + + @Override + public void setPlugin(Plugin plugin) { + super.setPlugin(plugin); + assert plugin instanceof CompositePlugin; + CompositePlugin compositePlugin = (CompositePlugin) plugin; + for (Plugin p : compositePlugin.getAllPlugins()) { + if (p instanceof FieldAccessHandler fah) + fieldAccessHandler = fah; + else if (p instanceof ContainerAccessHandler cah) + containerAccessHandler = cah; + } + assert fieldAccessHandler != null; + assert containerAccessHandler != null; + } + + // ---------- solver logic starts ---------- + /** + * Initializes pointer analysis. + */ + @Override + protected void initialize() { + containerConfig = ContainerConfig.config; + super.initialize(); + } + + /** + * Processes worklist entries until the worklist is empty. + */ + @Override + protected void analyze() { + while (!workList.isEmpty()) { + WorkList.Entry entry = workList.pollEntry(); + if (entry instanceof WorkList.PointerEntry pEntry) { + Pointer p = pEntry.pointer(); + PointsToSet pts = pEntry.pointsToSet(); + PointsToSet diff = propagate(p, pts); + if (!diff.isEmpty() && p instanceof CSVar v) { + processInstanceStore(v, diff); + processInstanceLoad(v, diff); + processArrayStore(v, diff); + processArrayLoad(v, diff); + processCall(v, diff); + plugin.onNewPointsToSet(v, diff); + } + } + else if (entry instanceof WorkList.CallEdgeEntry eEntry) + processCallEdge(eEntry.edge()); + else if (entry instanceof WorkList.SetStmtEntry sEntry) + fieldAccessHandler.onNewSetStatement(sEntry.csMethod(), sEntry.setStmt()); + else if (entry instanceof WorkList.GetStmtEntry gEntry) + fieldAccessHandler.onNewGetStatement(gEntry.csMethod(), gEntry.getStmt()); + else if (entry instanceof WorkList.HostEntry hEntry) { + Pointer p = hEntry.pointer(); + PointsToSet diff = processHostEntry(hEntry); + if (p instanceof CSVar csVar && !diff.isEmpty()) + containerAccessHandler.onNewHostEntry(csVar, diff, hEntry.kind()); + } + } + plugin.onFinish(); + } + + public boolean addRecoveredCallSite(CSCallSite csCallSite) { + return recoveredCallSites.add(csCallSite); + } + + public boolean isRecoveredCallSite(CSCallSite csCallSite) { + return recoveredCallSites.contains(csCallSite); + } + + String[] stopSigns = new String[]{"iterator(", "entrySet()", "keySet()", "values()", "Entry(", "Iterator("}; + + /* + * @param source: PFG edge s --> v, if source inside Transfer method, do not propagate ptsH + */ + public boolean needPropagateHost(PointerFlowEdge edge) { + // Todo: Here return var of Map.values() --> r is treated as Local_Assign + if (edge.kind() == FlowKind.RETURN) { + Var sourceVar = ((CSVar) edge.source()).getVar(); + JClass container = sourceVar.getMethod().getDeclaringClass(); + String methodString = sourceVar.getMethod().toString(); + // container, entryset type + if (containerConfig.isHostClass(container)) { + // source variable is inside function: iterator, entryset, keyset, values. do not propagate to host + for (String stopSign: stopSigns) { + if (methodString.contains(stopSign)) + return false; + } + // HashTable.elements() return Enumeration of HashTable.values(), .keys() return Enumeration of keys + if (isHashtableType(container.getType()) && (methodString.contains("elements()") || methodString.contains("keys()"))) + return false; + // vector.elements() return Enumeration of Vector.elements + return !isVectorType(container.getType()) || !methodString.contains("elements()"); + } + return true; + } + return true; + } + + /** + * @return true if the type of given expression is concerned in + * pointer analysis, otherwise false. + */ + public static boolean isConcerned(Exp exp) { + Type type = exp.getType(); + return type instanceof ReferenceType && !(type instanceof NullType); + } + + public void addCutStoreField(StoreField set) { // 需要跳过的StoreField,位于最内层的set方法 + cutStoreFields.add(set); + } + + private PointsToSet processHostEntry(WorkList.HostEntry entry) { + Pointer pointer = entry.pointer(); + PointsToSet hostSet = entry.hostSet(); + HostKind kind = entry.kind(); + PointsToSet diff = containerAccessHandler.getHostListOf(pointer, kind).addAllDiff(hostSet); + if (!diff.isEmpty()) { + pointerFlowGraph.getOutEdgesOf(pointer).forEach(edge -> { + if (needPropagateHost(edge)) { + Pointer target = edge.target(); + workList.addHostEntry(target, kind, diff); + } + }); + } + return diff; + } + + private void processInstanceStore(CSVar baseVar, PointsToSet pts) { + Context context = baseVar.getContext(); + Var var = baseVar.getVar(); + for (StoreField store : var.getStoreFields()) { + // skip cutStores + if (cutStoreFields.contains(store)) + continue; + Var fromVar = store.getRValue(); + if (propTypes.isAllowed(fromVar)) { + CSVar from = getCSManager().getCSVar(context, fromVar); + pts.forEach(baseObj -> { + JField field = store.getFieldRef().resolve(); + InstanceField instField = getCSManager().getInstanceField(baseObj, field); + addPFGEdge(from, instField, FlowKind.INSTANCE_STORE); + }); + } + } + } + + private void processInstanceLoad(CSVar baseVar, PointsToSet pts) { + Context context = baseVar.getContext(); + Var var = baseVar.getVar(); + for (LoadField load : var.getLoadFields()) { + Var toVar = load.getLValue(); + JField field = load.getFieldRef().resolveNullable(); + if (propTypes.isAllowed(toVar) && field != null) { + CSVar to = getCSManager().getCSVar(context, toVar); + pts.forEach(baseObj -> { + InstanceField instField = getCSManager().getInstanceField( + baseObj, field); + addPFGEdge(instField, to, //toVar.getType(), + isNonRelay(load) ? FlowKind.NON_RELAY_GET : FlowKind.INSTANCE_LOAD); + }); + } + } + } + + public void processCallEdge(Edge edge) { + if (callGraph.addEdge(edge)) { + if (edge instanceof ReflectiveCallEdge reflEdge) + setVirtualArg(reflEdge); + // process new call edge + CSMethod csCallee = edge.getCallee(); + addCSMethod(csCallee); + if (edge.getKind() != CallKind.OTHER && !isIgnored(csCallee.getMethod())) { + CSCallSite csCallSite = edge.getCallSite(); + Context callerCtx = edge.getCallSite().getContext(); + Invoke callSite = edge.getCallSite().getCallSite(); + Context calleeCtx = csCallee.getContext(); + JMethod callee = csCallee.getMethod(); + InvokeExp invokeExp = callSite.getInvokeExp(); + // pass arguments to parameters + for (int i = 0; i < invokeExp.getArgCount(); ++i) { + Var arg = invokeExp.getArg(i); + if (propTypes.isAllowed(arg)) { + Var param = callee.getIR().getParam(i); + CSVar argVar = getCSManager().getCSVar(callerCtx, arg); + CSVar paramVar = getCSManager().getCSVar(calleeCtx, param); + addPFGEdge(argVar, paramVar, FlowKind.PARAMETER_PASSING); + } + } + // pass results to LHS variable + Var lhs = callSite.getResult(); + if (!ContainerAccessHandler.CutReturnEdge(lhs, callee) || recoveredCallSites.contains(csCallSite)) { + if (lhs != null && propTypes.isAllowed(lhs)) { + CSVar csLHS = getCSManager().getCSVar(callerCtx, lhs); + for (Var ret : callee.getIR().getReturnVars()) { + if (propTypes.isAllowed(ret) && !cutReturnVars.contains(ret)) { + CSVar csRet = getCSManager().getCSVar(calleeCtx, ret); + addPFGEdge(csRet, csLHS, FlowKind.RETURN); + } + } + } + } + } + plugin.onNewCallEdge(edge); + } + } + + public void addPFGEdge(Pointer source, Pointer target, FlowKind kind, Set transfers) { + PointerFlowEdge edge = new PointerFlowEdge(kind, source, target); + if (pointerFlowGraph.addEdge(edge) != null) { + PointsToSet sourceSet = getPointsToSetOf(source); + PointsToSet targetSet = makePointsToSet(); + transfers.forEach(transfer -> { + if (edge.addTransfer(transfer)) { + PointsToSet transferSet = transfer.apply(edge, sourceSet); + targetSet.addAll(transferSet); + } + }); + if (!targetSet.isEmpty()) + addPointsTo(target, targetSet); + plugin.onNewPFGEdge(edge); + } + } + + @Override + public void addPFGEdge(PointerFlowEdge edge, Transfer transfer) { + edge = pointerFlowGraph.addEdge(edge); + if (edge != null) { + if (edge.addTransfer(transfer)) { + PointsToSet targetSet = transfer.apply(edge, getPointsToSetOf(edge.source())); + if (!targetSet.isEmpty()) + addPointsTo(edge.target(), targetSet); + } + plugin.onNewPFGEdge(edge); + } + } + + public void addSetStmtEntry(CSMethod csMethod, SetStatement setStmt) { + workList.addSetStmtEntry(csMethod, setStmt); + } + + public void addGetStmtEntry(CSMethod csMethod, GetStatement getStmt) { + workList.addGetStmtEntry(csMethod, getStmt); + } + + public void addHostEntry(Pointer pointer, HostKind kind, PointsToSet hostSet) { + if (!hostSet.isEmpty()) + workList.addHostEntry(pointer, kind, hostSet); + } + + public void addCutReturnVar(Var ret) { + cutReturnVars.add(ret); + } + + public void addSelectedMethod(JMethod method) { selectedMethods.add(method); } + + public Set getInvolvedMethods() { + return selectedMethods; + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/core/solver/DefaultSolver.java b/src/main/java/pascal/taie/analysis/pta/core/solver/DefaultSolver.java index f0313dce3..fa9062bdf 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/solver/DefaultSolver.java +++ b/src/main/java/pascal/taie/analysis/pta/core/solver/DefaultSolver.java @@ -122,7 +122,7 @@ public class DefaultSolver implements Solver { private final PointsToSetFactory ptsFactory; - private final PropagateTypes propTypes; + protected final PropagateTypes propTypes; /** * Whether only analyzes application code. @@ -141,27 +141,27 @@ public class DefaultSolver implements Solver { */ private volatile boolean isTimeout; - private Plugin plugin; + protected Plugin plugin; - private WorkList workList; + protected WorkList workList; - private CSCallGraph callGraph; + protected CSCallGraph callGraph; - private PointerFlowGraph pointerFlowGraph; + protected PointerFlowGraph pointerFlowGraph; - private Set reachableMethods; + protected Set reachableMethods; /** * Set of classes that have been initialized. */ - private Set initializedClasses; + protected Set initializedClasses; /** * Set of methods to be intercepted and ignored. */ - private Set ignoredMethods; + protected Set ignoredMethods; - private StmtProcessor stmtProcessor; + protected StmtProcessor stmtProcessor; private PointerAnalysisResult result; @@ -174,6 +174,7 @@ public DefaultSolver(AnalysisOptions options, HeapModel heapModel, this.csManager = csManager; hierarchy = World.get().getClassHierarchy(); typeSystem = World.get().getTypeSystem(); + callGraph = new CSCallGraph(csManager); ptsFactory = new PointsToSetFactory(csManager.getObjectIndexer()); propTypes = new PropagateTypes( (List) options.get("propagate-types"), @@ -251,8 +252,7 @@ public void solve() { /** * Initializes pointer analysis. */ - private void initialize() { - callGraph = new CSCallGraph(csManager); + protected void initialize() { pointerFlowGraph = new PointerFlowGraph(csManager); workList = new WorkList(); reachableMethods = Sets.newSet(); @@ -304,7 +304,7 @@ private void stop() { /** * Processes work list entries until the work list is empty. */ - private void analyze() { + protected void analyze() { while (!workList.isEmpty() && !isTimeout) { // phase starts while (!workList.isEmpty() && !isTimeout) { @@ -340,7 +340,7 @@ private void analyze() { * Propagates pointsToSet to pt(pointer) and its PFG successors, * returns the difference set of pointsToSet and pt(pointer). */ - private PointsToSet propagate(Pointer pointer, PointsToSet pointsToSet) { + protected PointsToSet propagate(Pointer pointer, PointsToSet pointsToSet) { logger.trace("Propagate {} to {}", pointsToSet, pointer); Set> filters = pointer.getFilters(); if (!filters.isEmpty()) { @@ -400,7 +400,8 @@ private void processInstanceLoad(CSVar baseVar, PointsToSet pts) { JField field = load.getFieldRef().resolve(); pts.forEach(baseObj -> { if (baseObj.getObject().isFunctional()) { - InstanceField instField = csManager.getInstanceField(baseObj, field); + InstanceField instField = csManager.getInstanceField( + baseObj, field); addPFGEdge(instField, to, FlowKind.INSTANCE_LOAD); } }); @@ -414,7 +415,7 @@ private void processInstanceLoad(CSVar baseVar, PointsToSet pts) { * @param arrayVar the array variable * @param pts set of new discovered arrays pointed by the variable. */ - private void processArrayStore(CSVar arrayVar, PointsToSet pts) { + protected void processArrayStore(CSVar arrayVar, PointsToSet pts) { Context context = arrayVar.getContext(); Var var = arrayVar.getVar(); for (StoreArray store : var.getStoreArrays()) { @@ -441,7 +442,7 @@ private void processArrayStore(CSVar arrayVar, PointsToSet pts) { * @param arrayVar the array variable * @param pts set of new discovered arrays pointed by the variable. */ - private void processArrayLoad(CSVar arrayVar, PointsToSet pts) { + protected void processArrayLoad(CSVar arrayVar, PointsToSet pts) { Context context = arrayVar.getContext(); Var var = arrayVar.getVar(); for (LoadArray load : var.getLoadArrays()) { @@ -464,7 +465,7 @@ private void processArrayLoad(CSVar arrayVar, PointsToSet pts) { * @param recv the receiver variable * @param pts set of new discovered objects pointed by the variable. */ - private void processCall(CSVar recv, PointsToSet pts) { + protected void processCall(CSVar recv, PointsToSet pts) { Context context = recv.getContext(); Var var = recv.getVar(); for (Invoke callSite : var.getInvokes()) { @@ -531,7 +532,7 @@ private void processCallEdge(Edge edge) { } } - private boolean isIgnored(JMethod method) { + protected boolean isIgnored(JMethod method) { return ignoredMethods.contains(method) || onlyApp && !method.isApplication(); } @@ -546,7 +547,7 @@ private void processNewMethod(JMethod method) { } } - private class StmtProcessor { + protected class StmtProcessor { /** * Information shared by all visitors. diff --git a/src/main/java/pascal/taie/analysis/pta/core/solver/PointerFlowGraph.java b/src/main/java/pascal/taie/analysis/pta/core/solver/PointerFlowGraph.java index 5375fb9a7..5279dfe7e 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/solver/PointerFlowGraph.java +++ b/src/main/java/pascal/taie/analysis/pta/core/solver/PointerFlowGraph.java @@ -22,6 +22,7 @@ package pascal.taie.analysis.pta.core.solver; +import pascal.taie.analysis.graph.flowgraph.FlowEdge; import pascal.taie.analysis.graph.flowgraph.FlowKind; import pascal.taie.analysis.pta.core.cs.element.CSManager; import pascal.taie.analysis.pta.core.cs.element.Pointer; @@ -33,6 +34,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; + /** * Represents pointer flow graph in context-sensitive pointer analysis. */ @@ -59,9 +61,7 @@ public PointerFlowEdge addEdge(PointerFlowEdge edge) { } @Override - public Set> getInEdgesOf(Pointer node) { - throw new UnsupportedOperationException(); - } + public Set getInEdgesOf(Pointer node) { return node.getInEdges(); } @Override public Set getOutEdgesOf(Pointer pointer) { @@ -74,7 +74,8 @@ public Stream pointers() { @Override public Set getPredsOf(Pointer node) { - throw new UnsupportedOperationException(); + return Views.toMappedSet(node.getInEdges(), + PointerFlowEdge::target); } @Override diff --git a/src/main/java/pascal/taie/analysis/pta/core/solver/WorkList.java b/src/main/java/pascal/taie/analysis/pta/core/solver/WorkList.java index 780fdb1fe..143cc44f3 100644 --- a/src/main/java/pascal/taie/analysis/pta/core/solver/WorkList.java +++ b/src/main/java/pascal/taie/analysis/pta/core/solver/WorkList.java @@ -26,6 +26,8 @@ import pascal.taie.analysis.pta.core.cs.element.CSCallSite; import pascal.taie.analysis.pta.core.cs.element.CSMethod; import pascal.taie.analysis.pta.core.cs.element.Pointer; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.HostKind; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.*; import pascal.taie.analysis.pta.pts.PointsToSet; import pascal.taie.util.collection.Maps; @@ -38,7 +40,6 @@ * Represents work list in pointer analysis. */ final class WorkList { - /** * Pointer entries to be processed. */ @@ -49,6 +50,13 @@ final class WorkList { */ private final Queue> callEdges = new ArrayDeque<>(); + // Additional work lists for cut-shortcut analysis + private final Queue hostEntries = new ArrayDeque<>(); + + private final Queue setStmtEntries = new ArrayDeque<>(); + + private final Queue getStmtEntries = new ArrayDeque<>(); + void addEntry(Pointer pointer, PointsToSet pointsToSet) { PointsToSet set = pointerEntries.get(pointer); if (set != null) { @@ -66,19 +74,27 @@ Entry pollEntry() { if (!callEdges.isEmpty()) { // for correctness, we need to ensure that any call edges in // the work list must be processed prior to the pointer entries - return new CallEdgeEntry(callEdges.poll()); - } else if (!pointerEntries.isEmpty()) { + return new WorkList.CallEdgeEntry(callEdges.poll()); + } + else if (!pointerEntries.isEmpty()) { var it = pointerEntries.entrySet().iterator(); var e = it.next(); it.remove(); - return new PointerEntry(e.getKey(), e.getValue()); - } else { - throw new NoSuchElementException(); + return new WorkList.PointerEntry(e.getKey(), e.getValue()); } + else if (!setStmtEntries.isEmpty()) + return setStmtEntries.poll(); + else if (!getStmtEntries.isEmpty()) + return getStmtEntries.poll(); + else if (!hostEntries.isEmpty()) + return hostEntries.poll(); + else + throw new NoSuchElementException(); } boolean isEmpty() { - return pointerEntries.isEmpty() && callEdges.isEmpty(); + return pointerEntries.isEmpty() && hostEntries.isEmpty() && callEdges.isEmpty() + && setStmtEntries.isEmpty() && getStmtEntries.isEmpty(); } interface Entry { @@ -91,4 +107,28 @@ record PointerEntry(Pointer pointer, PointsToSet pointsToSet) record CallEdgeEntry(Edge edge) implements Entry { } + + record HostEntry(Pointer pointer, HostKind kind, PointsToSet hostSet) + implements WorkList.Entry { + } + + record SetStmtEntry(CSMethod csMethod, SetStatement setStmt) + implements WorkList.Entry { + } + + record GetStmtEntry(CSMethod csMethod, GetStatement getStmt) + implements WorkList.Entry { + } + + void addHostEntry(Pointer pointer, HostKind kind, PointsToSet hostSet) { + hostEntries.add(new HostEntry(pointer, kind, hostSet)); + } + + void addSetStmtEntry(CSMethod csMethod, SetStatement setStmt) { + setStmtEntries.add(new SetStmtEntry(csMethod, setStmt)); + } + + void addGetStmtEntry(CSMethod csMethod, GetStatement getStmt) { + getStmtEntries.add(new GetStmtEntry(csMethod, getStmt)); + } } diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/CompositePlugin.java b/src/main/java/pascal/taie/analysis/pta/plugin/CompositePlugin.java index 657c2de62..0b82f0edc 100644 --- a/src/main/java/pascal/taie/analysis/pta/plugin/CompositePlugin.java +++ b/src/main/java/pascal/taie/analysis/pta/plugin/CompositePlugin.java @@ -76,6 +76,10 @@ public void addPlugin(Plugin... plugins) { } } + public final List getAllPlugins() { + return allPlugins; + } + private void addPlugin(Plugin plugin, List plugins, String name, Class... parameterTypes) { try { diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/Plugin.java b/src/main/java/pascal/taie/analysis/pta/plugin/Plugin.java index 2886a1c07..3704b0ee1 100644 --- a/src/main/java/pascal/taie/analysis/pta/plugin/Plugin.java +++ b/src/main/java/pascal/taie/analysis/pta/plugin/Plugin.java @@ -28,6 +28,7 @@ import pascal.taie.analysis.pta.core.cs.element.CSMethod; import pascal.taie.analysis.pta.core.cs.element.CSObj; import pascal.taie.analysis.pta.core.cs.element.CSVar; +import pascal.taie.analysis.pta.core.solver.PointerFlowEdge; import pascal.taie.analysis.pta.core.solver.Solver; import pascal.taie.analysis.pta.pts.PointsToSet; import pascal.taie.ir.stmt.Invoke; @@ -129,4 +130,12 @@ default void onNewCSMethod(CSMethod csMethod) { */ default void onUnresolvedCall(CSObj recv, Context context, Invoke invoke) { } + + /** + * Invoked when pointer analysis discover a new PFG Edge + * + * @param edge the new created PFG edge + */ + default void onNewPFGEdge(PointerFlowEdge edge) { + } } diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/ResultProcessor.java b/src/main/java/pascal/taie/analysis/pta/plugin/ResultProcessor.java index 95721aae3..f5a5cfd2d 100644 --- a/src/main/java/pascal/taie/analysis/pta/plugin/ResultProcessor.java +++ b/src/main/java/pascal/taie/analysis/pta/plugin/ResultProcessor.java @@ -108,16 +108,17 @@ public static void process(AnalysisOptions options, boolean taintEnabled = options.getString("taint-config") != null || !((List) options.get("taint-config-providers")).isEmpty(); + boolean onlyDumpApp = options.getBoolean("only-dump-app"); if (options.getBoolean("dump")) { dumpPointsToSet(result, taintEnabled); } if (options.getBoolean("dump-ci")) { - dumpCIPointsToSet(result); + dumpCIPointsToSet(result, onlyDumpApp); } if (options.getBoolean("dump-yaml")) { - dumpPointsToSetInYaml(result); + dumpPointsToSetInYaml(result, onlyDumpApp); } String expectedFile = options.getString("expected-file"); @@ -198,7 +199,7 @@ private static void dumpPointers( out.println(); } - private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { + private static void dumpPointsToSetInYaml(PointerAnalysisResult result, boolean onlyDumpApp) { File outFile = new File(World.get().getOptions().getOutputDir(), RESULTS_YAML_FILE); logger.info("Dumping points-to set (with contexts) in YAML to {}", outFile.getAbsolutePath()); @@ -220,6 +221,7 @@ private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { // - "[]:NewObj{[0@L1] new A}" final var variables = result.getCSVars() .stream() + .filter(csVar -> !onlyDumpApp || csVar.getVar().getMethod().isApplication()) .collect(Collectors.groupingBy(csVar -> csVar.getVar().getMethod().getSignature(), Maps::newOrderedMap, Collectors.collectingAndThen( @@ -250,6 +252,7 @@ private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { // - "[]:NewObj{[0@L1] new String}" final var staticFields = result.getStaticFields() .stream() + .filter(staticField -> !onlyDumpApp || staticField.getField().isApplication()) .collect(Collectors.groupingBy(sField -> sField.getField().getDeclaringClass().getName(), Maps::newOrderedMap, Collectors.mapping(sField -> Maps.ofLinkedHashMap( @@ -268,6 +271,7 @@ private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { // - "[]:NewObj{[0@L1] new String}" final var instanceFields = result.getInstanceFields() .stream() + .filter(instanceField -> !onlyDumpApp || instanceField.getField().isApplication()) .collect(Collectors.groupingBy(iField -> iField.getBase().getObject(), () -> Maps.newOrderedMap(objComparator), Collectors.collectingAndThen( @@ -297,6 +301,7 @@ private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { // - "ConstantObj{java.lang.String: \"hello\"}" final var arrayIndexes = result.getArrayIndexes() .stream() + .filter(arrayIndex -> !onlyDumpApp || (arrayIndex.getArray().getObject().getContainerMethod().isPresent() && arrayIndex.getArray().getObject().getContainerMethod().get().isApplication())) .collect(Collectors.groupingBy(ai -> ai.getArray().getObject(), () -> Maps.newOrderedMap(objComparator), Collectors.collectingAndThen( @@ -334,7 +339,7 @@ private static void dumpPointsToSetInYaml(PointerAnalysisResult result) { /** * Dumps points-to sets for all variables (without contexts). */ - private static void dumpCIPointsToSet(PointerAnalysisResult result) { + private static void dumpCIPointsToSet(PointerAnalysisResult result, boolean onlyDumpApp) { File outFile = new File(World.get().getOptions().getOutputDir(), CI_RESULTS_FILE); try (PrintStream out = new PrintStream(new FileOutputStream(outFile))) { logger.info("Dumping points-to set (without contexts) to {}", @@ -343,6 +348,7 @@ private static void dumpCIPointsToSet(PointerAnalysisResult result) { v -> v.getMethod().toString() + '/' + v.getName(); result.getVars() .stream() + .filter(v -> !onlyDumpApp || v.getMethod().isApplication()) .sorted(Comparator.comparing(toString)) .forEach(v -> { Set pts = result.getPointsToSet(v); diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/ReflectiveEdgeProperty.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/ReflectiveEdgeProperty.java new file mode 100644 index 000000000..f765faa3d --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/ReflectiveEdgeProperty.java @@ -0,0 +1,44 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut; + +import pascal.taie.analysis.pta.plugin.reflection.ReflectiveCallEdge; +import pascal.taie.ir.exp.Var; +import pascal.taie.language.type.ArrayType; +import pascal.taie.util.collection.Maps; + +import java.util.Map; + +import static pascal.taie.analysis.pta.core.solver.CutShortcutSolver.isConcerned; + +public class ReflectiveEdgeProperty { + // used for Cut-Shortcut + public enum ReflectiveCallKind { + NEW_INSTANCE, // r = c.newInstance(args), r is the baseVar + METHOD_INVOKE, // r = m.invoke(obj, args): m is a instance of class method, and obj is a instance of m's declaring method, + // args is a variable of array type which stores the argument of m in order + } + + private static final Map reflectiveCallKindMap = Maps.newMap(); + + private static final Map reflectiveCallVirtualArgMap = Maps.newMap(); + + public static void setReflectiveKind(ReflectiveCallEdge reflectiveCallEdge, ReflectiveCallKind kind) { + reflectiveCallKindMap.put(reflectiveCallEdge, kind); + } + + public static ReflectiveCallKind getReflectiveKind(ReflectiveCallEdge reflectiveCallEdge) { + return reflectiveCallKindMap.get(reflectiveCallEdge); + } + + public static void setVirtualArg(ReflectiveCallEdge reflectiveCallEdge) { + Var args = reflectiveCallEdge.getArgs(); + if (args != null && isConcerned(args)) { + Var virtualArg = new Var(args.getMethod(), "VirtualArg", ((ArrayType) args.getType()).elementType(), -1); + SpecialVariables.setVirtualVar(virtualArg); + reflectiveCallVirtualArgMap.put(reflectiveCallEdge, virtualArg); + } + } + + public static Var getVirtualArg(ReflectiveCallEdge reflectiveCallEdge) { + return reflectiveCallVirtualArgMap.getOrDefault(reflectiveCallEdge, null); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/SpecialVariables.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/SpecialVariables.java new file mode 100644 index 000000000..8efe9c0b7 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/SpecialVariables.java @@ -0,0 +1,52 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut; + +import pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.LoadField; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.Sets; + +import java.util.Map; +import java.util.Set; + +public class SpecialVariables { + private static final Set definedVars = Sets.newSet(); + + private static final Set virtualVars = Sets.newSet(); + + private static final Map definedParameterIndexes = Maps.newMap(); + + private static final Set relayedLoadFields = Sets.newSet(); + + public static void setDefined(Var var) { + definedVars.add(var); + } + + public static boolean isDefined(Var var) { + return definedVars.contains(var); + } + + public static void setVirtualVar(Var var) { + virtualVars.add(var); + } + + public static boolean isVirtualVar(Var var) { + return virtualVars.contains(var); + } + + public static void setParameterIndex(Var param, ParameterIndex index) { + definedParameterIndexes.put(param, index); + } + + public static ParameterIndex getParameterIndex(Var param) { + return definedParameterIndexes.get(param); + } + + public static void disableRelay(LoadField loadField) { + relayedLoadFields.add(loadField); + } + + public static boolean isNonRelay(LoadField loadField) { + return relayedLoadFields.contains(loadField); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ClassAndTypeClassifier.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ClassAndTypeClassifier.java new file mode 100644 index 000000000..3e46df1b3 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ClassAndTypeClassifier.java @@ -0,0 +1,56 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container; + +import pascal.taie.World; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContainerType; +import pascal.taie.language.classes.ClassHierarchy; +import pascal.taie.language.classes.JClass; +import pascal.taie.language.type.Type; +import pascal.taie.language.type.TypeSystem; + +public class ClassAndTypeClassifier { + private static final ClassHierarchy hierarchy = World.get().getClassHierarchy(); + private static final TypeSystem typeSystem = World.get().getTypeSystem(); + + private static final JClass mapClass = hierarchy.getClass("java.util.Map"); + private static final JClass mapEntryClass = hierarchy.getClass("java.util.Map$Entry"); + private static final JClass collectionClass = hierarchy.getClass("java.util.Collection"); + private static final JClass iteratorClass = hierarchy.getClass("java.util.Iterator"); + + private static final Type hashtableType = typeSystem.getType("java.util.Hashtable"); + private static final Type vectorType = typeSystem.getType("java.util.Vector"); + + public static ContainerType ClassificationOf(Type type) { + JClass clz = hierarchy.getClass(type.getName()); + return ClassificationOf(clz); + } + + public static ContainerType ClassificationOf(JClass clz) { + if (clz == null) + return ContainerType.OTHER; + else if (hierarchy.isSubclass(mapClass, clz)) + return ContainerType.MAP; + else if (hierarchy.isSubclass(collectionClass, clz)) + return ContainerType.COLLECTION; + else if (hierarchy.isSubclass(iteratorClass, clz)) + return ContainerType.ITER; + return ContainerType.OTHER; + } + + public static JClass getOuterClass(JClass inner) { + if (inner != null && inner.hasOuterClass()) + inner = inner.getOuterClass(); + return inner; + } + + public static boolean isVectorType(Type vector) { + return typeSystem.isSubtype(vectorType, vector); + } + + public static boolean isHashtableType(Type hashtable) { + return typeSystem.isSubtype(hashtableType, hashtable); + } + + public static boolean isMapEntryClass(JClass entry) { + return hierarchy.isSubclass(mapEntryClass, entry); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerAccessHandler.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerAccessHandler.java new file mode 100644 index 000000000..ff32346f0 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerAccessHandler.java @@ -0,0 +1,634 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container; + +import pascal.taie.analysis.graph.callgraph.CallGraph; +import pascal.taie.analysis.graph.callgraph.CallKind; +import pascal.taie.analysis.graph.callgraph.Edge; +import pascal.taie.analysis.graph.flowgraph.FlowKind; +import pascal.taie.analysis.pta.core.cs.context.Context; +import pascal.taie.analysis.pta.core.cs.element.*; +import pascal.taie.analysis.pta.core.heap.HeapModel; +import pascal.taie.analysis.pta.core.heap.Obj; +import pascal.taie.analysis.pta.core.solver.CutShortcutSolver; +import pascal.taie.analysis.pta.core.solver.PointerFlowEdge; +import pascal.taie.analysis.pta.core.solver.Solver; +import pascal.taie.analysis.pta.plugin.Plugin; +import pascal.taie.analysis.pta.plugin.cutshortcut.SpecialVariables; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ExtendType; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.HostKind; +import pascal.taie.analysis.pta.pts.PointsToSet; +import pascal.taie.ir.exp.InvokeExp; +import pascal.taie.ir.exp.InvokeInstanceExp; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.Cast; +import pascal.taie.ir.stmt.Invoke; +import pascal.taie.ir.stmt.New; +import pascal.taie.language.classes.JMethod; +import pascal.taie.language.type.ArrayType; +import pascal.taie.language.type.ClassType; +import pascal.taie.language.type.Type; +import pascal.taie.language.type.TypeSystem; +import pascal.taie.util.AnalysisException; +import pascal.taie.util.collection.*; + +import java.util.Collections; +import java.util.Set; + +import static pascal.taie.analysis.pta.core.solver.CutShortcutSolver.isConcerned; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.ClassAndTypeClassifier.*; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory.*; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.HostKind.*; +// ToDo: 其它自定义AbstractList类型,尤其是匿名内部类,实现了自定义get等方法 +public class ContainerAccessHandler implements Plugin { + private CutShortcutSolver solver; + + private CSManager csManager; + + private TypeSystem typeSystem; + + private HostManager hostManager; + + private HeapModel heapModel; + + private CallGraph callGraph; + + private ContainerConfig config; + + private final Set ContKindsInterested = Set.of(COL, MAP_KEY_SET, MAP_VALUES, MAP_ENTRY_SET); + private final MultiMap, Pair> HostPropagater = Maps.newMultiMap(); + + // The first key type of pointerHostListMap may not only be ''CSVar'': + // a container field of a object obj.list (InstanceField) may also point-to container object. + private final TwoKeyMap pointerHostListMap = Maps.newTwoKeyMap(); // ptsH + + // for Col-Value, Map-Value, Map-key exit calledge e: => , add -> + private final MultiMap> cachedContExitValues = Maps.newMultiMap(); + // for MapEntry-GetKey(Map-Key), MapEntry-GetValue(Map-Value), Iterator-next/previous/nextElement(Col-Value) l: r = v.next(..), add -> + private final MultiMap> cachedIterExitValues = Maps.newMultiMap(); + // \for v = new ArrayList(), l: r = v.get(0) => ArrayList.get, \in pts(), => \in hostToExit + private final MultiMap hostToExits = Maps.newMultiMap(); + + // assist worklist method to handle container extender + private final MultiMap ExtenderAddedToBase = Maps.newMultiMap(); // for , add v to + private final MultiMap ExtenderBaseToAdded = Maps.newMultiMap(); // for , add v1 to + + // process ArrayInitializer, l: add(v_d, v_s), where v_s is array, v_d is collection, for worklist, add a temp variable arr_l + private final MultiMap ArrayVarToVirtualArrayVar = Maps.newMultiMap(); // v_s --> arr_l + private final MultiMap CollectionVarToVirtualArrayVar = Maps.newMultiMap(); // v_d --> arr_l + + // We only model common container access. For user-defined container type like CustomArrayList, we usually ignore because they may define other entrance and exit method. + // For example, CustomArrayList.addAnObject, getAnObject. Since we have not model it, soundness issue could occur. + // But, there is [Entrance-Extend] method like List.addAll. If v is ArrayList, v.addAll(customArrayList). Where customArrayList may miss some src/dst variables. + // After propagate to v, v can not be soundly modeled. + // Hence here, taintHosts records those container objects whose type is modeled but may be influenced by unmodeled type container var like v. + private final Set taintHosts = Sets.newSet(); + + // Collect those var whose type is Map.Entry + private static final Set MapEntryVar = Sets.newSet(); + + public void setSolver(Solver solver) { + if (solver instanceof CutShortcutSolver cutShortcutSolver) { + this.solver = cutShortcutSolver; + csManager = solver.getCSManager(); + typeSystem = solver.getTypeSystem(); + callGraph = solver.getCallGraph(); + heapModel = solver.getHeapModel(); + config = ContainerConfig.config; + hostManager = new HostManager(csManager); + } + else + throw new AnalysisException("Invalid solver!"); + } + + @Override + public void onNewMethod(JMethod method) { + method.getIR().forEach(stmt -> { + if (stmt instanceof Cast castStmt) { + Type castType = castStmt.getRValue().getCastType(); + if (castType instanceof ClassType classType && isMapEntryClass(classType.getJClass())) + MapEntryVar.add(castStmt.getRValue().getValue()); + } + }); + } + + @Override + public void onNewCSMethod(CSMethod csMethod) { + Context context = csMethod.getContext(); + JMethod method = csMethod.getMethod(); + method.getIR().forEach(stmt -> { + if (stmt instanceof New newStmt) { + Obj obj = heapModel.getObj(newStmt); + Type objType = obj.getType(); + // called in Map.keySet()/values(), lhs = new KeySet()/values() + if (!method.isStatic()) { + CSVar csThis = csManager.getCSVar(context, method.getIR().getThis()); + CSVar csLHS = csManager.getCSVar(context, newStmt.getLValue()); + if (config.isKeySetClass(objType)) + HostPropagater.put(new Pair<>(HostKind.MAP, csThis), new Pair<>(MAP_KEY_SET, csLHS)); + else if (config.isValueSetClass(objType)) + HostPropagater.put(new Pair<>(HostKind.MAP, csThis), new Pair<>(MAP_VALUES, csLHS)); + } + } + }); + } + + // --> + @Override + public void onNewCallEdge(Edge edge) { + if (edge.getKind() == CallKind.OTHER) + return; + CSCallSite csCallSite = edge.getCallSite(); + CSMethod csCallee = edge.getCallee(); // + Invoke callSite = csCallSite.getCallSite(); // l + JMethod callee = csCallee.getMethod(); // m + Context callSiteContext = csCallSite.getContext(); // c + InvokeExp invokeExp = callSite.getInvokeExp(); // r = v.k(l_a1, ...) + + if (invokeExp instanceof InvokeInstanceExp instanceExp) { + Var base = instanceExp.getBase(); // v + CSVar csBase = csManager.getCSVar(callSiteContext, base); // + CSVar csThis = csManager.getCSVar(csCallee.getContext(), callee.getIR().getThis()); // + + // propagate ptsH to callee this + ContKindsInterested.forEach(hostKind -> { + PointsToSet baseHostMap = pointerHostListMap.getOrDefault(csBase, hostKind, null); + if (baseHostMap != null && !baseHostMap.isEmpty()) { + solver.addHostEntry(csThis, hostKind, baseHostMap); + HostPropagater.put(new Pair<>(hostKind, csBase), new Pair<>(hostKind, csThis)); + } + }); + + // Array Initializer + Pair arrayInitInfo = config.getArrayInitializer(callee); + if (arrayInitInfo != null) { + solver.addSelectedMethod(callee); + processArrayInitializer(csCallSite, arrayInitInfo, instanceExp); + } + // callee is [Container Entrance-Append] like v.add(E) + Set> categoryIndexPairs = config.getEntranceAppendIndex(callee); + if (!categoryIndexPairs.isEmpty()) + pointerHostListMap.getOrDefault(csBase, Collections.emptyMap()).forEach((hostKind, hostSet) -> + relateSourceToHosts(csCallSite, csCallee, categoryIndexPairs, hostSet)); + + // callee is [Container Entrance-Extend] like v.addAll(list) + Pair entranceExtendIndex = config.getEntranceExtendIndex(callee); + if (entranceExtendIndex != null) { + solver.addSelectedMethod(callee); + ExtendType extendType = entranceExtendIndex.first(); + Var addedContainer = instanceExp.getArg(entranceExtendIndex.second()); + processEntranceExtend(csBase, addedContainer, extendType); + } + + // callee is [Container Exit] + Var lhs = callSite.getLValue(); + if (lhs != null && isConcerned(lhs)) { + // Standard container-element exit method, like l: r = v.get(i);, v is Collection/Map + ContExitCategory exitCategory = config.getContainerExitCategory(callee); + if (exitCategory != null) { + solver.addSelectedMethod(callee); + cachedContExitValues.put(csBase, new Pair<>(callSite, exitCategory)); // prepare for new worklist entry + addTargetToAllHost(csBase, exitCategory == ColValue ? COL : HostKind.MAP, csCallSite, exitCategory, true); + } + + // MapEntry exit method, l: r = v.getKey(), v is MapEntry +// else if (config.isMapEntryGetKeyMethod(callee)) { +// solver.addSelectedMethod(callee); +// addTargetToAllHost(csBase, MAP_ENTRY, csCallSite, MapKey, false); +// cachedIterExitValues.put(csBase, new Triplet<>(callSite, MAP_ENTRY, MapKey)); +// } +// // MapEntry exit method, l: r = v.getValue(), v is MapEntry +// else if (config.isMapEntryGetValueMethod(callee)) { +// solver.addSelectedMethod(callee); +// addTargetToAllHost(csBase, MAP_ENTRY, csCallSite, MapValue, false); +// cachedIterExitValues.put(csBase, new Triplet<>(callSite, MAP_ENTRY, MapValue)); +// } + // iterator-element exit method, l: r = v.next()/previous()/nextElement(), v is iterator/enumeration + else if (config.isIteratorExitMethods(callee)) { + solver.addSelectedMethod(callee); + /* an example, HashSet.iterator() return HashMap.KeyIterator type. + * vs = new HashSet() {allocate os}, is = vs.iterator(), vsl = is.next(); + * vm = new HashMap() {allocate om}, im = vm.keyIterator(), vml = im.next(); + * same next call, the former is os --ColValue--> vsl, later is om --KeyValue--> vml, but with same next method + * difference is that is => COL_ITR while im => MAP_KEY_ITR + */ + addTargetToAllHost(csBase, MAP_VALUE_ITR, csCallSite, MapValue, false); + cachedIterExitValues.put(csBase, new Triplet<>(callSite, MAP_VALUE_ITR, MapValue)); + addTargetToAllHost(csBase, MAP_KEY_ITR, csCallSite, MapKey, false); + cachedIterExitValues.put(csBase, new Triplet<>(callSite, MAP_KEY_ITR, MapKey)); + addTargetToAllHost(csBase, COL_ITR, csCallSite, ColValue, false); + cachedIterExitValues.put(csBase, new Triplet<>(callSite, COL_ITR, ColValue)); + } + } + } + } + + // --> , where m is initializer of array List, l_a0/m_p0 are usually arrays of object + private void processArrayInitializer(CSCallSite csCallSite, Pair arrayInitInfo, InvokeInstanceExp instanceExp) { + Var arrayVar = instanceExp.getArg(arrayInitInfo.first()), // l_ai, copied array + collectionVar = arrayInitInfo.second() == -1 ? instanceExp.getBase(): instanceExp.getArg(arrayInitInfo.second()); // v + if (!(arrayVar.getType() instanceof ArrayType)) + throw new AnalysisException("Not Array Type!"); + Type elementType = ((ArrayType) arrayVar.getType()).elementType(); + // virtual var for m + Context callSiteContext = csCallSite.getContext(); + JMethod caller = csCallSite.getCallSite().getContainer(); + Var virtualArrayVar = new Var(caller, + "virtualArrayVar[" + caller.getName() + ", line:" + csCallSite.getCallSite().getLineNumber() +"]", + elementType, -1); // arr_arg_caller + SpecialVariables.setVirtualVar(virtualArrayVar); + ArrayVarToVirtualArrayVar.put(arrayVar, virtualArrayVar); // vs --> arr_l + CollectionVarToVirtualArrayVar.put(collectionVar, virtualArrayVar); // vd --> arr_l + CSVar csArray = csManager.getCSVar(callSiteContext, arrayVar); // + CSVar csVirtualArray = csManager.getCSVar(callSiteContext, virtualArrayVar); // + + solver.getPointsToSetOf(csArray).forEach(csObj -> { // \forall \in pts() + ArrayIndex arrayIndex = csManager.getArrayIndex(csObj); // + // --> + solver.addPFGEdge(arrayIndex, csVirtualArray, FlowKind.VIRTUAL_ARRAY, elementType); + }); + + CSVar csCollection = csManager.getCSVar(callSiteContext, collectionVar); // + // \forall \in ptsH(), --> + getHostListOf(csCollection, COL).forEach(hostObj -> addSourceToHost(csVirtualArray, hostObj, ColValue)); + } + + /* + * process c, l: r = v.k(..., v1, ...), where v and v1 are container variable, callee is extend method like List.addAll/Map.putAll + * @params csBaseContainer: // + * @params csAddedContainer: // + */ + private void processEntranceExtend(CSVar csBaseContainer, Var addedContainer, ExtendType extendType) { + // Collection/Map.Keyset()/Map.Values() -> Collection + CSVar csAddedContainer = csManager.getCSVar(csBaseContainer.getContext(), addedContainer); + ExtenderAddedToBase.put(csAddedContainer, csBaseContainer.getVar()); + ExtenderBaseToAdded.put(csBaseContainer, addedContainer); + + if (extendType == ExtendType.ColToCol) { + PointsToSet baseContainerSet = getHostListOf(csBaseContainer, COL); + addHostSubsetRelation(baseContainerSet, getHostListOf(csAddedContainer, COL), ExtendType.ColToCol); + addHostSubsetRelation(baseContainerSet, getHostListOf(csAddedContainer, MAP_KEY_SET), ExtendType.MapKeySetToCol); + addHostSubsetRelation(baseContainerSet, getHostListOf(csAddedContainer, MAP_VALUES), ExtendType.MapValuesToCol); + } + // Map -> Map + else if (extendType == ExtendType.MapToMap) { + PointsToSet baseMapSet = getHostListOf(csBaseContainer, HostKind.MAP); + addHostSubsetRelation(baseMapSet, getHostListOf(csAddedContainer, HostKind.MAP), ExtendType.MapToMap); + } + // Map.keySet() -> Col + else if (extendType == ExtendType.MapKeySetToCol) { + PointsToSet baseContainerSet = getHostListOf(csBaseContainer, COL); + addHostSubsetRelation(baseContainerSet, getHostListOf(csAddedContainer, MAP_KEY_SET), ExtendType.MapKeySetToCol); + } + } + + private void addHostSubsetRelation(PointsToSet baseSet, PointsToSet addedSet, ExtendType extendType) { + for (CSObj csAddedObj: addedSet) { + // if added object is not modeled, then both base/added container variable should not be optimized by Cut-Shotrcut + if (config.isUnmodeledClass(csAddedObj.getObject().getType()) || taintHosts.contains(csAddedObj)) { + baseSet.forEach(this::taintHost); + break; + } + baseSet.forEach(csBaseObj -> { + // can not add object to empty collection type + if (csBaseObj.getObject().getType().getName().contains("java.util.Collections$Empty")) + return; + switch (extendType) { + // Collection -> Collection + case ColToCol -> { + CSVar csAddedHostPointer = hostManager.getHostVar(csAddedObj, ColValue), + csBaseHostPointer = hostManager.getHostVar(csBaseObj, ColValue); + solver.addPFGEdge(csAddedHostPointer, csBaseHostPointer, FlowKind.SUBSET); + } + // Map -> Map + case MapToMap -> { + CSVar csAddedMapKeyHostPointer = hostManager.getHostVar(csAddedObj, MapKey), + csBaseMapKeyHostPointer = hostManager.getHostVar(csBaseObj, MapKey); + solver.addPFGEdge(csAddedMapKeyHostPointer, csBaseMapKeyHostPointer, FlowKind.SUBSET); + + CSVar csAddedMapValueHostPointer = hostManager.getHostVar(csAddedObj, MapValue), + csBaseMapValueHostPointer = hostManager.getHostVar(csBaseObj, MapValue); + solver.addPFGEdge(csAddedMapValueHostPointer, csBaseMapValueHostPointer, FlowKind.SUBSET); + } + // Map.keySet() -> Collection + case MapKeySetToCol -> { + CSVar csAddedMapKeyHostPointer = hostManager.getHostVar(csAddedObj, MapKey), + csBaseHostPointer = hostManager.getHostVar(csBaseObj, ColValue); + solver.addPFGEdge(csAddedMapKeyHostPointer, csBaseHostPointer, FlowKind.SUBSET); + } + // Map.values() -> Collection + case MapValuesToCol -> { + CSVar csAddedMapValueHostPointer = hostManager.getHostVar(csAddedObj, MapValue), + csBaseHostPointer = hostManager.getHostVar(csBaseObj, ColValue); + solver.addPFGEdge(csAddedMapValueHostPointer, csBaseHostPointer, FlowKind.SUBSET); + } + } + }); + } + } + + /* + * process when container object is added into pts() + * @params csVar: + * @params pts: {, .., } added to pts() + */ + @Override + public void onNewPointsToSet(CSVar csVar, PointsToSet pts) { + PointsToSet mapSet = solver.makePointsToSet(), + colSet = solver.makePointsToSet(); + pts.forEach(csObj -> { // + // process when oi is container object + Type objType = csObj.getObject().getType(); // Type(oi) + switch (ClassificationOf(objType)) { + case MAP -> mapSet.addObject(csObj); + case COLLECTION -> colSet.addObject(csObj); + } + + // if v is arguments of ArrayInitializer like ArrayList.(array), add PFG edge: --> + ArrayVarToVirtualArrayVar.get(csVar.getVar()).forEach(arrVar -> { + CSVar csArrVar = csManager.getCSVar(csVar.getContext(), arrVar); // + ArrayIndex arrayIndex = csManager.getArrayIndex(csObj); // + solver.addPFGEdge(arrayIndex, csArrVar, FlowKind.VIRTUAL_ARRAY, arrVar.getType()); // --> + }); + }); + // [MapHost], ptsH(, Map).update(MapSet) + if (!mapSet.isEmpty()) { + getHostListOf(csVar, HostKind.MAP).addAll(mapSet); + onNewHostEntry(csVar, mapSet, HostKind.MAP); + } + // [ColHost], ptsH(, Col).update(ColSet) + if (!colSet.isEmpty()) { + getHostListOf(csVar, COL).addAll(colSet); + onNewHostEntry(csVar, colSet, COL); + } + } + + /* + * @params csVar: + * @params entrySet: {, ... , } , new added to ptsH([host]) + * @params hostkind: type of host variable (MapKey, MapValue, ColValue, ColItr, etc.) + */ + public void onNewHostEntry(CSVar csVar, PointsToSet hostSet, HostKind hostKind) { + TransferAndMapEntryExit(csVar, hostSet, hostKind); + ProcessCachedExitInvokes(csVar, hostSet, hostKind); // process [HostTarget], propagate ptsH to exit variables + Context context = csVar.getContext(); // c + Var base = csVar.getVar(); // v + // process [HostSource] + base.getInvokes().forEach(invoke -> { + CSCallSite csCallSite = csManager.getCSCallSite(context, invoke); // + callGraph.getCalleesOf(csCallSite).forEach(csMethod -> { + Set> entranceInfo = config.getEntranceAppendIndex(csMethod.getMethod()); + // propagate ptsH to this variable of container class. + if (!entranceInfo.isEmpty()) + relateSourceToHosts(csCallSite, csMethod, entranceInfo, hostSet); + }); + }); + + // \forall arr_l -> v, must be collection type + if (hostKind == COL) { + CollectionVarToVirtualArrayVar.get(base).forEach(arrVar -> { + CSVar csArrVar = csManager.getCSVar(context, arrVar); + hostSet.forEach(hostObj -> addSourceToHost(csArrVar, hostObj, ColValue)); + }); + } + + // \forall v.addAll(v1) cached, since {, ...} added to ptsH(v), we need to update PFG xx -> according to ptsH(v1) + ExtenderBaseToAdded.get(csVar).forEach(addedContainer -> { // + CSVar csAddedContainer = csManager.getCSVar(csVar.getContext(), addedContainer); + // Collection/Map.keySet()/Map.values() -> Collection, where v can only be collection + if (hostKind == COL) { + addHostSubsetRelation(hostSet, getHostListOf(csAddedContainer, COL), ExtendType.ColToCol); + addHostSubsetRelation(hostSet, getHostListOf(csAddedContainer, MAP_KEY_SET), ExtendType.MapKeySetToCol); + addHostSubsetRelation(hostSet, getHostListOf(csAddedContainer, MAP_VALUES), ExtendType.MapValuesToCol); + } + // Map -> Map, v can only be map + else if (hostKind == HostKind.MAP) + addHostSubsetRelation(hostSet, getHostListOf(csAddedContainer, HostKind.MAP), ExtendType.MapToMap); + }); + + // \forall vc.addAll(v) cached, since {, ...} added to ptsH(v), we need to update PFG -> xx according to ptsH(vc) + ExtenderAddedToBase.get(csVar).forEach(baseContainer -> { // + CSVar csBaseContainer = csManager.getCSVar(csVar.getContext(), baseContainer); + // Collection -> Collection + if (hostKind == COL) + addHostSubsetRelation(getHostListOf(csBaseContainer, COL), hostSet, ExtendType.ColToCol); + // Map.keySet() -> Collection + else if (hostKind == MAP_KEY_SET) + addHostSubsetRelation(getHostListOf(csBaseContainer, COL), hostSet, ExtendType.MapKeySetToCol); + // Collection -> Collection + else if (hostKind == MAP_VALUES) + addHostSubsetRelation(getHostListOf(csBaseContainer, COL), hostSet, ExtendType.MapValuesToCol); + // Map -> Map, vc can only be map + else if (hostKind == HostKind.MAP) + addHostSubsetRelation(getHostListOf(csBaseContainer, HostKind.MAP), hostSet, ExtendType.MapToMap); + }); + + HostPropagater.get(new Pair<>(hostKind, csVar)).forEach(kindCSVarPair -> + solver.addHostEntry(kindCSVarPair.second(), kindCSVarPair.first(), hostSet)); + } + + /* + * @params csCallSite: , v is a container variable + * @params csCallee: , m is a container access method + * @params categoryWithindex: {, ...}. For example, Map.put(k, v) -> {, } + * @params hostSet: ptsH() => {, ... , } + */ + private void relateSourceToHosts(CSCallSite csCallSite, CSMethod csCallee, Set> categoryIndexPairs, PointsToSet hostSet) { + JMethod callee = csCallee.getMethod(); // m + ClassType classType = callee.getDeclaringClass().getType(); // container class type + solver.addSelectedMethod(callee); + for (Pair categoryIndexPair: categoryIndexPairs) { + Var argument = csCallSite.getCallSite().getInvokeExp().getArg(categoryIndexPair.second()); // source argument, l_ai + CSVar csArg = csManager.getCSVar(csCallSite.getContext(), argument); // + // filter: csObj must be subtype of container class type can be transfer to this variable + // do: generate source relation: <--category-- + hostSet.getObjects().stream().filter(csObj -> typeSystem.isSubtype(classType, csObj.getObject().getType())) + .forEach(csObj -> addSourceToHost(csArg, csObj, categoryIndexPair.first())); + } + + } + + /* + * [TransferHost] + * @params csVar: + * @params containerType: type of container (Map or Collection) + * @params hostSet: {, ... , } , new added to ptsH([containerType]) + */ + private void TransferAndMapEntryExit(CSVar csVar, PointsToSet hostSet, HostKind hostKind) { + Var varBase = csVar.getVar(); // container variable v + Context context = csVar.getContext(); // c + varBase.getInvokes().forEach(invoke -> { // l: r = v.k(...) + Var lhs = invoke.getLValue(); // r + if (lhs == null || !isConcerned(lhs)) + return; + InvokeExp invokeExp = invoke.getInvokeExp(); // r = v.k(...) + String invokeString = invokeExp.getMethodRef().getName(); + CSVar csLHS = csManager.getCSVar(context, lhs); // + + // Transfer Method: List/Set.iterator, Map.entrySet, Map.keySet, Map.values, MapEntry,iterator, MapEntry.next, etc. + config.getTransferAPIs().forEach(((hostKindOri, methodStr, hostKindGen) -> { + if (hostKind == hostKindOri && invokeString.contains(methodStr)) + solver.addHostEntry(csLHS, hostKindGen, hostSet); + })); + + // Transfer Method: Vector.elements(), Hashtable.elements() + switch (hostKind) { + // Vector.elements() --> col_itr,注意其它list type没有elements方法 + case COL -> { + if (invokeString.equals("elements") && isVectorType(varBase.getType())) + solver.addHostEntry(csLHS, COL_ITR, hostSet); + } + // Hashtable.elements() --> map_value_itr, 注意其它map type没有elements方法 + case MAP -> { + if ((invokeString.equals("elements") && isHashtableType(varBase.getType()))) + solver.addHostEntry(csLHS, MAP_VALUE_ITR, hostSet); + } + } + + // Map-Entry Exit: map.entry.getKey(), map.entry.getValue() + config.getMapEntryExits().forEach((hostKindOri, methodStr, category) -> { + if (hostKind == hostKindOri && invokeString.contains(methodStr)) + hostSet.forEach(host -> + checkHostRelatedExit(csManager.getCSCallSite(context, invoke), host, category)); + }); + }); + } + + // process related target invoke: l: r = v.k() for + private void ProcessCachedExitInvokes(CSVar csVar, PointsToSet hostSet, HostKind hostKind) { + // container-exit like: r = v.get(..) + cachedContExitValues.get(csVar).forEach(invokeCategoryPair -> { // out invokes + Invoke callSite = invokeCategoryPair.first(); // l: r = v.k(...) + hostSet.forEach(host -> { + if (typeSystem.isSubtype(csVar.getVar().getType(), host.getObject().getType())) + checkHostRelatedExit(csManager.getCSCallSite(csVar.getContext(), callSite), host, invokeCategoryPair.second()); + }); + }); + + // iterator-exit like: r = v.next(), here v is Iterator type, not Container type. Hence do not check type with typeSystem.isSubtype + cachedIterExitValues.get(csVar).forEach(invokeTrip -> { + // v could be COL_ITR/MAP_KEY_ITR/MAP_VALUE_ITR, so first we need to match + if (hostKind != invokeTrip.second()) + return; + Invoke callSite = invokeTrip.first(); // l: r = v.k(...)> + hostSet.forEach(host -> + checkHostRelatedExit(csManager.getCSCallSite(csVar.getContext(), callSite), host, invokeTrip.third())); + }); + } + + // for exit calledge e: => , \forall \in ptsH([hostKind]), add --exitCategory--> + private void addTargetToAllHost(CSVar csBase, HostKind hostKind, CSCallSite csCallSite, ContExitCategory exitCategory, boolean checkType) { + getHostListOf(csBase, hostKind).forEach(csObj -> { + if (!checkType || typeSystem.isSubtype(csBase.getType(), csObj.getObject().getType())) + checkHostRelatedExit(csCallSite, csObj, exitCategory); + }); + } + + /* + * [HostTarget], --category--> + * @params csVar: + * @params csObj: + * @params category: category of host variable (MapKey, MapValue, ColValue) + */ + private void addTargetToHost(CSVar csVar, CSObj csObj, ContExitCategory category) { + if (hostManager.addHostTarget(csObj, category, csVar)) { + CSVar hostPointer = hostManager.getHostVar(csObj, category); // + solver.addPFGEdge(hostPointer, csVar, FlowKind.HOST_TO_RESULT); // result.getType(), + } + } + + /* + * [HostSource], <--category-- + * @params csVar: + * @params csObj: + * @params category: category of host variable (MapKey, MapValue, ColValue) + */ + private void addSourceToHost(CSVar csArg, CSObj csObj, ContExitCategory category) { + if (isConcerned(csArg.getVar()) && hostManager.addHostSource(csObj, category, csArg)) { + CSVar hostPointer = hostManager.getHostVar(csObj, category); // + solver.addPFGEdge(csArg, hostPointer, FlowKind.ARG_TO_HOST); + } + } + + // get ptsH for a pointer, usually pointer is a container variable csVar: . + // Sometimes it could be a InstanceField like .list + public PointsToSet getHostListOf(Pointer pointer, HostKind hostKind) { + return pointerHostListMap.computeIfAbsent(pointer, hostKind, (v, t) -> solver.makePointsToSet()); + } + + // propagate ptsH along PFG edge + @Override + public void onNewPFGEdge(PointerFlowEdge edge) { + Pointer source = edge.source(), target = edge.target(); + if (solver.needPropagateHost(edge)) { + pointerHostListMap.getOrDefault(source, Collections.emptyMap()).forEach(( + (hostKind, sourcePtsH) -> solver.addHostEntry(target, hostKind, sourcePtsH))); + } + } + + // process l: r = v.k(...) => m + public static boolean CutReturnEdge(Var lhs, JMethod callee) { + // if m is a container-exit method, cut m_ret --> r + if (ContainerConfig.config.getContainerExitCategory(callee) != null) + return true; + // if m is a iterator.next method, cut m_ret --> r, + // here MapEntryVar is to make sure iterator is not Map.entrySet().iterator(), because that next() is Transfer not Exit. + if (ContainerConfig.config.getIterExitCategory(callee) != null && MapEntryVar.contains(lhs)) + return true; + // if m is a modeled map.entry.getKey()/getValue(), cut m_ret --> r + if (ContainerConfig.config.isHostClass(getOuterClass(callee.getDeclaringClass())) + && isMapEntryClass(callee.getDeclaringClass()) + && (callee.getName().equals("getKey")) || callee.getName().equals("getValue")) + return true; + return false; + } + + + // For [Entrance-Extend] generated PFG edges: src --> dst, both src and dst are host variables for container objects. + // If src may point to a object whose type is not modeled, those host var should be conservatively handled. + private void taintHost(CSObj hostObj) { + if (!taintHosts.contains(hostObj)) { + taintHosts.add(hostObj); + hostToExits.get(hostObj).forEach(this::recoverCallSite); + for (ContExitCategory cat: ContExitCategory.values()) { + hostManager.getHostVar(hostObj, cat).getOutEdges().forEach(outEdge -> { + Pointer succ = outEdge.target(); + if (succ instanceof CSVar csVar) { + Obj targetHostObj = hostManager.getHostObj(csVar.getVar()); + if (targetHostObj != null) + taintHost(csManager.getCSObj(csVar.getContext(), targetHostObj)); + } + }); + } + } + } + + // callsite l: r = v.get(), whose callee may be cut. However, since v may point-to a object whose type is not modeled + // the PFG from callee to r should be recovered to conservatively model + private void recoverCallSite(CSCallSite csCallSite) { + if (solver.addRecoveredCallSite(csCallSite)) { + CSVar csLHS = csManager.getCSVar(csCallSite.getContext(), csCallSite.getCallSite().getLValue()); + callGraph.getCalleesOf(csCallSite).forEach(csCallee -> { + JMethod callee = csCallee.getMethod(); + callee.getIR().getReturnVars().forEach(ret -> { + CSVar csRet = csManager.getCSVar(csCallee.getContext(), ret); // + solver.addPFGEdge(csRet, csLHS, FlowKind.RETURN); // --> + }); + }); + } + } + + // call edge e: => is a exit-method call-relation. + private void checkHostRelatedExit(CSCallSite csCallSite, CSObj hostObj, ContExitCategory category) { + Var lhs = csCallSite.getCallSite().getLValue(); // r + // this callsite can be soundly modeled by cut-shortcut + if (!solver.isRecoveredCallSite(csCallSite)) { + if (config.isUnmodeledClass(hostObj.getObject().getType()) || taintHosts.contains(hostObj)) + recoverCallSite(csCallSite); + else { + hostToExits.put(hostObj, csCallSite); + addTargetToHost(csManager.getCSVar(csCallSite.getContext(), lhs), hostObj, category); + } + } + } + +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerConfig.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerConfig.java new file mode 100644 index 000000000..61167eb2b --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/ContainerConfig.java @@ -0,0 +1,157 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container; + +import pascal.taie.World; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.*; +import pascal.taie.language.classes.ClassHierarchy; +import pascal.taie.language.classes.JClass; +import pascal.taie.language.classes.JMethod; +import pascal.taie.language.type.ClassType; +import pascal.taie.language.type.Type; +import pascal.taie.util.collection.*; + +import java.util.Map; +import java.util.Set; + +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory.MapKey; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory.MapValue; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.HostKind.*; +import static pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.IterExitCategory.*; + +public class ContainerConfig { + public static ContainerConfig config = new ContainerConfig(); + + private static final ClassHierarchy hierarchy = World.get().getClassHierarchy(); + + // main modeled collection/map classes + private final MultiMap hostClasses = Maps.newMultiMap(); + // other abstract collection/map classes + private final MultiMap unmodeledClasses = Maps.newMultiMap(); + + // Entrance Append method to argument index: m -> k, store for methods like List.add(k), Map.put(k, v) + private final MultiMap> EntranceAppendMap = Maps.newMultiMap(); + + // Entrance Extend method to argument index: m -> k, store for methods like List.addAll(c), Map.putAll(m) + private final Map> EntranceExtendMap = Maps.newMap(); + + // Exit method to category: m -> c, c could be one of [Map-Key, Map-Value, Col-Value] + private final Map ContainerExitMap = Maps.newMap(); + // Exit method - Iterator.previous()/next()/nextElement() + // Here call-relation between Map.Entry.getKey/getValue() not resolved by Tai-e, hence not modeled here. + private final Map IterExitMap = Maps.newMap(); + + private final TwoKeyMap TransferAPIs = Maps.newTwoKeyMap(); + private final TwoKeyMap MapEntryExits = Maps.newTwoKeyMap(); + + // for m: ArrayList., add 0 -> -1, meaning array of index 0 copied to base + private final Map> ArrayInitializer = Maps.newMap(); + + private ContainerConfig() { + initializeTransferAPIs(); + } + + private void initializeTransferAPIs() { + TransferAPIs.put(COL, "iterator", COL_ITR); + TransferAPIs.put(COL, "Iterator", COL_ITR); + TransferAPIs.put(MAP, "entrySet", MAP_ENTRY_SET); + TransferAPIs.put(MAP, "keySet", MAP_KEY_SET); + TransferAPIs.put(MAP, "values", MAP_VALUES); + TransferAPIs.put(MAP, "Entry", MAP_ENTRY); + TransferAPIs.put(MAP, "keys", MAP_KEY_ITR); + TransferAPIs.put(MAP_ENTRY_SET, "iterator", MAP_ENTRY_ITR); + TransferAPIs.put(MAP_VALUES, "iterator", MAP_VALUE_ITR); + TransferAPIs.put(MAP_KEY_SET, "iterator", MAP_KEY_ITR); + TransferAPIs.put(MAP_ENTRY_ITR, "next", MAP_ENTRY); + + MapEntryExits.put(MAP_ENTRY, "getValue", MapValue); + MapEntryExits.put(MAP_ENTRY, "getKey", MapKey); + } + + public void addIterExitCategory(String methodSig, IterExitCategory category) { + JMethod method = hierarchy.getMethod(methodSig); + if (method != null) + IterExitMap.put(method, category); + } + + public IterExitCategory getIterExitCategory(JMethod method) { return IterExitMap.getOrDefault(method, null); } + + public boolean isIteratorExitMethods(JMethod method) { return getIterExitCategory(method) == ColIter; } + + public boolean isKeySetClass(Type type) { + if (type instanceof ClassType classType) + return hostClasses.get(ContainerType.KEYSET).contains(classType.getJClass()); + return false; + } + + public boolean isValueSetClass(Type type) { + if (type instanceof ClassType classType) + return hostClasses.get(ContainerType.VALUES).contains(classType.getJClass()); + return false; + } + + public void addEntranceAppendIndex(String methodSig, ContExitCategory category, Integer index) { + JMethod method = hierarchy.getMethod(methodSig); + if (method != null) + EntranceAppendMap.put(method, new Pair<>(category, index)); + } + + public Set> getEntranceAppendIndex(JMethod method) { return EntranceAppendMap.get(method); } + + public void addEntranceExtendIndex(String methodSig, ExtendType extendType, Integer index) { + JMethod method = hierarchy.getMethod(methodSig); + EntranceExtendMap.put(method, new Pair<>(extendType, index)); + } + + public Pair getEntranceExtendIndex(JMethod method) { return EntranceExtendMap.getOrDefault(method, null); } + + public void addContainerExitCategory(String methodSig, ContExitCategory category) { + JMethod method = hierarchy.getMethod(methodSig); + if (method != null) + ContainerExitMap.put(method, category); + } + + public ContExitCategory getContainerExitCategory(JMethod method) { + return ContainerExitMap.getOrDefault(method, null); + } + + public TwoKeyMap getTransferAPIs() { + return TransferAPIs; + } + + public TwoKeyMap getMapEntryExits() { + return MapEntryExits; + } + + public void addHostClass(ContainerType containerType, String clzName) { + JClass clz = hierarchy.getClass(clzName); + if (clz != null) + hostClasses.put(containerType, clz); + } + + public boolean isHostClass(JClass clz) { return hostClasses.values().contains(clz); } + + public void resolveUnmodeledClasses() { + hierarchy.getAllSubclassesOf(hierarchy.getClass("java.util.Collection")).forEach(clz -> { + if (!clz.isAbstract() && !hostClasses.values().contains(clz)) + unmodeledClasses.put(ContainerType.COLLECTION, clz); + }); + hierarchy.getAllSubclassesOf(hierarchy.getClass("java.util.Map")).forEach(clz -> { + if (!clz.isAbstract() && !hostClasses.values().contains(clz)) + unmodeledClasses.put(ContainerType.MAP, clz); + }); + } + + + public boolean isUnmodeledClass(Type type) { + if (type instanceof ClassType classType) + return unmodeledClasses.values().contains(classType.getJClass()); + return false; + } + + public void addArrayInitializer(String smethod, int index0, int index1) { + // index0: index of array variable, index1: index of Collection variable + JMethod method = hierarchy.getMethod(smethod); + ArrayInitializer.put(method, new Pair<>(index0, index1)); + } + + public Pair getArrayInitializer(JMethod method) { return ArrayInitializer.get(method); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/HostManager.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/HostManager.java new file mode 100644 index 000000000..cd4d43f32 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/HostManager.java @@ -0,0 +1,55 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container; + +import pascal.taie.analysis.pta.core.cs.element.CSManager; +import pascal.taie.analysis.pta.core.cs.element.CSObj; +import pascal.taie.analysis.pta.core.cs.element.CSVar; +import pascal.taie.analysis.pta.core.heap.Obj; +import pascal.taie.analysis.pta.plugin.cutshortcut.SpecialVariables; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory; +import pascal.taie.ir.exp.Var; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.TwoKeyMultiMap; + +import java.util.Map; + +public class HostManager { + private final CSManager csManager; + + // assign a host variable for each container object + private final Map hostMap = Maps.newMap(); + // map host variable to host object + private final Map hostVarToObj = Maps.newMap(); + + // denotes [HostSource], mapping container object to set of host variables that container.entrance(v), where o \in pts(container) + private final TwoKeyMultiMap HostSourceMap = Maps.newTwoKeyMultiMap(); + + // denotes [HostTarget], mapping container object to set of host variables that v = container.exit(), where o \in pts(container) + private final TwoKeyMultiMap HostTargetMap = Maps.newTwoKeyMultiMap(); + + public HostManager(CSManager csManager) { + this.csManager = csManager; + } + + public CSVar getHostVar(CSObj containerCSObj, ContExitCategory category) { + Obj containerObj = containerCSObj.getObject(); + + Var hostVar = hostMap.computeIfAbsent(containerObj, obj -> { + String varName = "host of[" + obj.toString() + "]," + category.getCategory(); + Var newHostVar = new Var(obj.getContainerMethod().orElse(null), varName, containerObj.getType(), -1); + SpecialVariables.setVirtualVar(newHostVar); + hostVarToObj.put(newHostVar, obj); + return newHostVar; + }); + return csManager.getCSVar(containerCSObj.getContext(), hostVar); + } + + public boolean addHostSource(CSObj containerCSObj, ContExitCategory category, CSVar hostVar) { + return HostSourceMap.put(containerCSObj, category, hostVar); + } + + public boolean addHostTarget(CSObj containerCSObj, ContExitCategory category, CSVar hostVar) { + return HostTargetMap.put(containerCSObj, category, hostVar); + } + + public Obj getHostObj(Var var) { return hostVarToObj.getOrDefault(var, null); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/MakeDefaultContainerConfig.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/MakeDefaultContainerConfig.java new file mode 100644 index 000000000..61c5da851 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/MakeDefaultContainerConfig.java @@ -0,0 +1,94 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContExitCategory; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ContainerType; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.ExtendType; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.enums.IterExitCategory; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class MakeDefaultContainerConfig { + public static void make() { + ContainerConfig config = ContainerConfig.config; + try { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + // [Map/Col] classes + Map> rawHostClassesData = mapper.readValue( + Thread.currentThread().getContextClassLoader().getResourceAsStream("container-config/host-classes.yml"), + new TypeReference<>() {}); + rawHostClassesData.forEach((containerName, containerClasses) -> { + ContainerType containerType = ContainerType.getTypeName(containerName); + containerClasses.forEach(containerClassName -> + config.addHostClass(containerType, containerClassName)); + }); + + // [Other Class] + config.resolveUnmodeledClasses(); + + // [Entrance-Append] + Map>> rawEntranceAppendDatas = mapper.readValue( + Thread.currentThread().getContextClassLoader().getResourceAsStream("container-config/entrance-append.yml"), + new TypeReference<>() {}); + rawEntranceAppendDatas.forEach((categoryName, APIInfos) -> { + ContExitCategory category = ContExitCategory.getCategory(categoryName); + APIInfos.stream().filter(Objects::nonNull).forEach(APIInfo -> { + String methodSig = (String) APIInfo.get("signature"); + Integer index = (Integer) APIInfo.get("index"); + config.addEntranceAppendIndex(methodSig, category, index); + }); + }); + + // [Entrance-Extend] + Map>> rawEntranceExtendDatas = mapper.readValue( + Thread.currentThread().getContextClassLoader().getResourceAsStream("container-config/entrance-extend.yml"), + new TypeReference<>() {}); + rawEntranceExtendDatas.forEach((extendTypeValue, APIInfos) -> { + ExtendType extendType = ExtendType.getExtendType(extendTypeValue); + APIInfos.stream().filter(Objects::nonNull).forEach(APIInfo -> { + String methodSig = (String) APIInfo.get("signature"); + Integer index = (Integer) APIInfo.get("index"); + config.addEntranceExtendIndex(methodSig, extendType, index); + }); + + }); + + // [Entrance-Array Initializer] + Map>> rawArrayInitializerDatas = mapper.readValue( + Thread.currentThread().getContextClassLoader().getResourceAsStream("container-config/array-initializer.yml"), + new TypeReference<>() {}); + rawArrayInitializerDatas.get("ArrayInit").forEach(APIInfo -> { + String methodSig = (String) APIInfo.get("signature"); + Integer srcIndex = (Integer) APIInfo.get("src"); + Integer dstIndex = (Integer) APIInfo.get("dst"); + config.addArrayInitializer(methodSig, srcIndex, dstIndex); + }); + + // [Exit] + Map> rawExitDatas = mapper.readValue( + Thread.currentThread().getContextClassLoader().getResourceAsStream("container-config/exit.yml"), + new TypeReference<>() {}); + rawExitDatas.forEach((exitType, methodsigs) -> { + ContExitCategory category = ContExitCategory.getCategory(exitType); + IterExitCategory iterExitCategory = IterExitCategory.getCategory(exitType); + // Container-Exit + if (category != null) + methodsigs.stream().filter(Objects::nonNull).forEach(methodsig -> config.addContainerExitCategory(methodsig, category)); + // Iter-Exit + else if (iterExitCategory != null) + methodsigs.stream().filter(Objects::nonNull).forEach(methodsig -> config.addIterExitCategory(methodsig, iterExitCategory)); + }); + + } + catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContExitCategory.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContExitCategory.java new file mode 100644 index 000000000..21cd3838b --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContExitCategory.java @@ -0,0 +1,27 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container.enums; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum ContExitCategory { + MapKey("MapKey"), + MapValue("MapValue"), + ColValue("ColValue"); + + private final String category; + + ContExitCategory(String category) { + this.category = category; + } + + public String getCategory() { + return category; + } + + private final static Map NameToCateGory = Arrays.stream(values()) + .collect(Collectors.toMap(c -> c.category, Function.identity())); + + public static ContExitCategory getCategory(String Name) { return NameToCateGory.getOrDefault(Name, null); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContainerType.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContainerType.java new file mode 100644 index 000000000..b055f35e2 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ContainerType.java @@ -0,0 +1,31 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container.enums; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum ContainerType { + MAP("Map"), + COLLECTION("Col"), + ENTRYSET("EntrySet"), // entrySet of map + KEYSET("KeySet"), // keySet of map + VALUES("Values"), // values of map + ITER("Iter"), + OTHER("OTHER"); + + private final String typeName; + + ContainerType(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + private final static Map NameToContainerType = Arrays.stream(values()) + .collect(Collectors.toMap(c -> c.typeName, Function.identity())); + + public static ContainerType getTypeName(String Name) { return NameToContainerType.getOrDefault(Name, null); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ExtendType.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ExtendType.java new file mode 100644 index 000000000..01506d209 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/ExtendType.java @@ -0,0 +1,24 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container.enums; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum ExtendType { + MapToMap("MapToMap"), // Map.putAll(Map) + ColToCol("ColToCol"), // Collection.addAll(Collection) + MapKeySetToCol("MapKeySetToCol"), // Collection.addAll(Map.keySet()) + MapValuesToCol("MapValuesToCol"); // Collection.addAll(Map.values()); + + private final String value; + + public String getValue() { return value; } + + ExtendType(String _value) { value = _value; } + + private final static Map ValueToExtendType = Arrays.stream(values()) + .collect(Collectors.toMap(c -> c.value, Function.identity())); + + public static ExtendType getExtendType(String value) { return ValueToExtendType.getOrDefault(value, null); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/HostKind.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/HostKind.java new file mode 100644 index 000000000..18a1dea65 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/HostKind.java @@ -0,0 +1,26 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container.enums; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum HostKind { + MAP("Map"), MAP_ENTRY("MapEntry"), + MAP_ENTRY_SET("MapEntrySet"), MAP_KEY_SET("MapKeySet"), MAP_VALUES("MapValues"), + MAP_KEY_ITR("MapKeyIter"), MAP_ENTRY_ITR("MapEntryIter"), MAP_VALUE_ITR("MapValueIter"), + + COL("Col"), + COL_ITR("ColIter"); + + private String kind; + + HostKind(String _kind) { kind = _kind; } + + public String getKind() { return kind; } + + private final static Map NameToHostKind = Arrays.stream(values()) + .collect(Collectors.toMap(h -> h.kind, Function.identity())); + + public static HostKind getHostKind(String Name) { return NameToHostKind.get(Name); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/IterExitCategory.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/IterExitCategory.java new file mode 100644 index 000000000..fc87e5e88 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/container/enums/IterExitCategory.java @@ -0,0 +1,27 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.container.enums; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public enum IterExitCategory { + ColIter("ColIter"), + MapGetValue("MapGetValue"), + MapGetKey("MapGetKey"); + + private final String category; + + IterExitCategory(String category) { + this.category = category; + } + + public String getCategory() { + return category; + } + + private final static Map NameToCateGory = Arrays.stream(values()) + .collect(Collectors.toMap(c -> c.category, Function.identity())); + + public static IterExitCategory getCategory(String Name) { return NameToCateGory.getOrDefault(Name, null); } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractLoadField.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractLoadField.java new file mode 100644 index 000000000..5bca090a5 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractLoadField.java @@ -0,0 +1,30 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.ir.exp.FieldAccess; +import pascal.taie.ir.exp.InstanceFieldAccess; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.FieldStmt; +import pascal.taie.ir.stmt.StmtVisitor; + +public class AbstractLoadField extends FieldStmt { + private final boolean terminate; + + public AbstractLoadField(Var lvalue, FieldAccess rvalue, boolean terminate) { + super(lvalue, rvalue); + this.terminate = terminate; + } + + @Override + public FieldAccess getFieldAccess() { + return getRValue(); + } + + public boolean isNonRelay() { + return !terminate; + } + + @Override + public T accept(StmtVisitor visitor) { + return null; + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractStoreField.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractStoreField.java new file mode 100644 index 000000000..1546c5c12 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/AbstractStoreField.java @@ -0,0 +1,23 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.ir.exp.FieldAccess; +import pascal.taie.ir.exp.InstanceFieldAccess; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.FieldStmt; +import pascal.taie.ir.stmt.StmtVisitor; + +public class AbstractStoreField extends FieldStmt { + public AbstractStoreField(FieldAccess lvalue, Var rvalue) { + super(lvalue, rvalue); + } + + @Override + public FieldAccess getFieldAccess() { + return getLValue(); + } + + @Override + public T accept(StmtVisitor visitor) { + return null; + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/FieldAccessHandler.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/FieldAccessHandler.java new file mode 100644 index 000000000..d907e2536 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/FieldAccessHandler.java @@ -0,0 +1,392 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.World; +import pascal.taie.analysis.graph.callgraph.CallGraph; +import pascal.taie.analysis.graph.callgraph.Edge; +import pascal.taie.analysis.graph.flowgraph.FlowKind; +import pascal.taie.analysis.pta.core.cs.context.Context; +import pascal.taie.analysis.pta.core.cs.element.*; +import pascal.taie.analysis.pta.core.solver.CutShortcutSolver; +import pascal.taie.analysis.pta.core.solver.PointerFlowEdge; +import pascal.taie.analysis.pta.core.solver.Solver; +import pascal.taie.analysis.pta.plugin.Plugin; +import pascal.taie.analysis.pta.plugin.cutshortcut.container.ContainerAccessHandler; +import pascal.taie.analysis.pta.pts.PointsToSet; +import pascal.taie.ir.IR; +import pascal.taie.ir.exp.InstanceFieldAccess; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.proginfo.FieldRef; +import pascal.taie.ir.stmt.Invoke; +import pascal.taie.ir.stmt.LoadField; +import pascal.taie.ir.stmt.StoreField; +import pascal.taie.language.classes.JClass; +import pascal.taie.language.classes.JField; +import pascal.taie.language.classes.JMethod; +import pascal.taie.language.type.TypeSystem; +import pascal.taie.util.AnalysisException; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.MultiMap; +import pascal.taie.util.collection.Sets; + +import java.util.*; +import java.util.stream.Collectors; + +import static pascal.taie.analysis.pta.core.solver.CutShortcutSolver.isConcerned; +import static pascal.taie.analysis.pta.plugin.cutshortcut.SpecialVariables.*; +import static pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex.THISINDEX; +import static pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex.getRealParameterIndex; + +public class FieldAccessHandler implements Plugin { + private final MultiMap setStatements = Maps.newMultiMap(); + + private final MultiMap getStatements = Maps.newMultiMap(); + + private final Set cutReturnVars = Sets.newSet(); + + private final Map> abstractLoadFields = Maps.newMap(); + + private final Map> abstractStoreFields = Maps.newMap(); + + private CutShortcutSolver solver; + + private TypeSystem typeSystem; + + private CSManager csManager; + + private CallGraph callGraph; + + private Context emptyContext; + + @Override + public void setSolver(Solver solver) { + if (solver instanceof CutShortcutSolver cutShortcutSolver) { + this.solver = cutShortcutSolver; + typeSystem = World.get().getTypeSystem(); + callGraph = solver.getCallGraph(); + csManager = solver.getCSManager(); + emptyContext = solver.getContextSelector().getEmptyContext(); + } + else + throw new AnalysisException("Invalid Solver to " + getClass()); + } + + @Override + public void onNewPointsToSet(CSVar csVar, PointsToSet pts) { + processAbstractInstanceLoad(csVar, pts); + processAbstractInstanceStore(csVar, pts); + } + + // process abstract instance load: , \forall \in pts(), add --> + private void processAbstractInstanceLoad(CSVar csVar, PointsToSet pts) { + Context baseContext = csVar.getContext(); // c + Var base = csVar.getVar(); // lhs + getAbstractLoadFields(base).forEach(load -> { + Var lhs = load.getLValue(); + JField field = load.getFieldRef().resolve(); + if (isConcerned(lhs) && field != null) { + CSVar csLHS = csManager.getCSVar(baseContext, lhs); + pts.forEach(baseObj -> { + if (typeSystem.isSubtype(field.getDeclaringClass().getType(), baseObj.getObject().getType())) { + InstanceField instField = csManager.getInstanceField(baseObj, field); + solver.addPFGEdge(instField, csLHS, // lhs.getType(), + load.isNonRelay() ? FlowKind.NON_RELAY_GET : FlowKind.GET); + } + }); + } + }); + } + + // process abstract store: , \forall \in pts(), add --> + private void processAbstractInstanceStore(CSVar csVar, PointsToSet pts) { + Context baseContext = csVar.getContext(); + Var base = csVar.getVar(); + getAbstractStoreFields(base).forEach(store -> { + Var rhs = store.getRValue(); + JField field = store.getFieldRef().resolve(); + if (isConcerned(rhs) && field != null) { + CSVar csRHS = csManager.getCSVar(baseContext, rhs); + pts.forEach(baseObj -> { + if (typeSystem.isSubtype(field.getDeclaringClass().getType(), baseObj.getObject().getType())) { + InstanceField instField = csManager.getInstanceField(baseObj, field); + solver.addPFGEdge(csRHS, instField, FlowKind.SET, field.getType()); + } + }); + } + }); + } + + // [CutStore], [CutPropLoad] + @Override + public void onNewMethod(JMethod method) { + if (method.isAbstract()) + return; + IR methodIR = method.getIR(); + methodIR.forEach(stmt -> { + if (stmt.getDef().isPresent() && stmt.getDef().get() instanceof Var def) + setDefined(def); + }); + if (methodIR.getThis() != null) + setParameterIndex(methodIR.getThis(), THISINDEX); + List params = methodIR.getParams(); + for (int i = 0; i < params.size(); i++) + setParameterIndex(params.get(i), getRealParameterIndex(i)); + } + + @Override + public void onNewCSMethod(CSMethod csMethod) { + JMethod method = csMethod.getMethod(); + if (method.isAbstract()) + return; + JClass declaringClass = method.getDeclaringClass(); + method.getIR().forEach(stmt -> { + if (!declaringClass.getName().equals("java.awt.Component") + && !declaringClass.getName().equals("javax.swing.JComponent") + && stmt instanceof LoadField load + && load.getFieldAccess() instanceof InstanceFieldAccess fieldAccess) { + // m_ret(x) = m_pi.f; + Var x = load.getLValue(), y = fieldAccess.getBase(); + if (isConcerned(x)) { + int retIndex = ParameterIndex.GetReturnVariableIndex(x); + ParameterIndex baseIndex = getParameterIndex(y); + if (retIndex >= 0 && baseIndex != null && !isDefined(y)) { + // the load inst should be relayed + disableRelay(load); + addCutReturnVar(csMethod, x); + GetStatement getStatement = new GetStatement(retIndex, baseIndex, load.getFieldRef()); + solver.addGetStmtEntry(csMethod, getStatement); + } + } + } + // not entry method, each entry method is associated with empty context, so this condition can be used to filter non-entry method + else if (!callGraph.entryMethods().collect(Collectors.toSet()).contains(csManager.getCSMethod(emptyContext, method)) + && stmt instanceof StoreField store + && store.getFieldAccess() instanceof InstanceFieldAccess fieldAccess) { + // m_pi.f = m_pj; + Var x = fieldAccess.getBase(), y = store.getRValue(); + if (isConcerned(y)) { + ParameterIndex baseIndex = getParameterIndex(x), rhsIndex = getParameterIndex(y); + if (baseIndex != null && rhsIndex != null && !isDefined(x) && !isDefined(y)) { + solver.addCutStoreField(store); + SetStatement setStatement = new SetStatement(baseIndex, store.getFieldRef(), rhsIndex); + solver.addSetStmtEntry(csMethod, setStatement); + } + } + } + }); + } + + /* + * work when l first appears in + * @param csMethod: , + * @param setStmt: l: m_pi.f = m_pj \in m + */ + public void onNewSetStatement(CSMethod csMethod, SetStatement setStmt) { + if (addSetStatement(csMethod, setStmt)) + callGraph.edgesInTo(csMethod).forEach( + edge -> processSetStatementOnCallEdge(edge, setStmt)); + } + + /* + * work when l first appears in + * @param csMethod: , + * @param getStmt: l: m_ret = m_pi.f \in m + */ + public void onNewGetStatement(CSMethod csMethod, GetStatement getStmt) { + // add deleted return vars (only do it when a new set statement is found) + if (addGetStatement(csMethod, getStmt)) + callGraph.edgesInTo(csMethod).forEach( + edge -> processGetStatementOnCallEdge(edge, getStmt)); + } + + /* + * [PropStore], generate temp store: or concrete abstract store: + * @param edge: --> , + * @param setStmt: m_pi.f = m_pj \in m + */ + private void processSetStatementOnCallEdge(Edge edge, SetStatement setStmt) { + Var base = ParameterIndex.getCorrespondingArgument(edge, setStmt.baseIndex()), // l_ai + rhs = ParameterIndex.getCorrespondingArgument(edge, setStmt.rhsIndex()); // l_aj + if (base != null && rhs != null && isConcerned(rhs)) { + CSMethod csCaller = edge.getCallSite().getContainer(); // + ParameterIndex baseIndexAtCaller = getParameterIndex(base), // l_ai \is m_pu of m_caller + rhsIndexAtCaller = getParameterIndex(rhs); // l_aj \is m_pv of m_caller + if (baseIndexAtCaller != null && rhsIndexAtCaller != null && !isDefined(base) && !isDefined(rhs)) { + solver.addSelectedMethod(edge.getCallee().getMethod()); + // setStmt: m_pu.f = m_pv + solver.addSetStmtEntry(csCaller, new SetStatement(baseIndexAtCaller, setStmt.fieldRef(), rhsIndexAtCaller)); + } + // top-level temp store, generate a new store field in current context + else + processNewAbstractStoreField(edge.getCallSite().getContext(), base, setStmt.fieldRef(), rhs); + } + } + + /** + * [CutPropLoad], add new return var m_ret, generate temp load: + * @param edge: --> + * @param getStmt: m_ret = m_pi.f \in m + */ + private void processGetStatementOnCallEdge(Edge edge, GetStatement getStmt) { + // ToDo: consider context-sensitive setting + Invoke callSite = edge.getCallSite().getCallSite(); // l + JMethod callee = edge.getCallee().getMethod(); // m + Var base = ParameterIndex.getCorrespondingArgument(edge, getStmt.baseIndex()), // l_ai + lhs = callSite.getLValue(); // m_caller_ret + if (!ContainerAccessHandler.CutReturnEdge(lhs, callee)) { + if (base != null && lhs != null && isConcerned(lhs)) { + CSMethod csCaller = callGraph.getContainerOf(edge.getCallSite()); // + int lhsIndexAtCaller = ParameterIndex.GetReturnVariableIndex(lhs); // index of m_caller_ret + ParameterIndex baseIndexAtCaller = getParameterIndex(base); // index of l_ai + solver.addSelectedMethod(edge.getCallee().getMethod()); + // m_caller_ret is return value of m_caller, l_ai is parameter/this of m_caller, [PropLoad] to caller of m_caller + if (lhsIndexAtCaller != -1 && baseIndexAtCaller != null) { + addCutReturnVar(csCaller, lhs); // 每一层GetStatement的retVar都需要删除 + solver.addGetStmtEntry(csCaller, new GetStatement(lhsIndexAtCaller, baseIndexAtCaller, getStmt.fieldRef())); + // new generated PFG edges are non-relay + processNewAbstractLoadField(edge.getCallSite().getContext(), lhs, base, getStmt.fieldRef(), false); + } + // top-level temp load, generate a new load: , new generated PFG edge is terminal(relayed) + else + processNewAbstractLoadField(edge.getCallSite().getContext(), lhs, base, getStmt.fieldRef(), true); + } + } + } + + // [ShortcutStore]: in context c, generate a new store field: + private void processNewAbstractStoreField(Context context, Var base, FieldRef fieldRef, Var rhs) { + CSVar csBase = csManager.getCSVar(context, base), // + csRHS = csManager.getCSVar(context, rhs); // + JField field = fieldRef.resolve(); + AbstractStoreField storeField = new AbstractStoreField(new InstanceFieldAccess(fieldRef, base), rhs); + getAbstractStoreFields(base).add(storeField); + // \forall \in pts(), add --> + solver.getPointsToSetOf(csBase).forEach(csObj -> { + if (typeSystem.isSubtype(field.getDeclaringClass().getType(), csObj.getObject().getType())) + solver.addPFGEdge(csRHS, csManager.getInstanceField(csObj, field), FlowKind.SET, field.getType()); + }); + } + + // [ShortcutLoad]: in context c, generate a new abstract load field: , terminate determines whether it is lhs = base.f terminal PFG edge + private void processNewAbstractLoadField(Context context, Var lhs, Var base, FieldRef fieldRef, boolean terminate) { + CSVar csBase = csManager.getCSVar(context, base), // + csLHS = csManager.getCSVar(context, lhs); // + JField field = fieldRef.resolve(); + AbstractLoadField loadField = new AbstractLoadField(lhs, new InstanceFieldAccess(fieldRef, base), terminate); + getAbstractLoadFields(base).add(loadField); + // \forall \in pts(), add PFG edge: --> + solver.getPointsToSetOf(csBase).forEach(csObj -> { + if (typeSystem.isSubtype(field.getDeclaringClass().getType(), csObj.getObject().getType())) + solver.addPFGEdge(csManager.getInstanceField(csObj, field), csLHS, // lhs.getType(), + terminate ? FlowKind.GET : FlowKind.NON_RELAY_GET); + }); + } + + // [RelayEdge]: --> + @Override + public void onNewCallEdge(Edge edge) { + Invoke callSite = edge.getCallSite().getCallSite(); // l + processSetStatementOnNewCallEdge(edge); + Var lhs = callSite.getLValue(); // r + if (lhs != null && isConcerned(lhs)) { + processGetStatementOnNewCallEdge(edge); + CSMethod csCallee = edge.getCallee(); // + JMethod callee = csCallee.getMethod(); // m + CSVar csLHS = csManager.getCSVar(edge.getCallSite().getContext(), lhs); // + if (!ContainerAccessHandler.CutReturnEdge(lhs, callee)) { + for (Var ret: callee.getIR().getReturnVars()) { + if (cutReturnVars.contains(ret)) { + CSVar csRet = csManager.getCSVar(csCallee.getContext(), ret); // + csRet.getInEdges().forEach(inEdge -> { // \forall --> + // transfer, add --> , inEdge.terminate = True + if (inEdge.kind() != FlowKind.NON_RELAY_GET) + solver.addPFGEdge(inEdge.source(), csLHS, inEdge.kind(), inEdge.getTransfers()); + }); + } + } + } + } + } + + // [CutPropLoad]: --> , + private void processSetStatementOnNewCallEdge(Edge edge) { + // when a new call edge found, process callSite with previous found SetStatement in the callee + getSetStatementsOf(edge.getCallee()).forEach(setStmt -> + processSetStatementOnCallEdge(edge, setStmt)); + } + + // --> + private void processGetStatementOnNewCallEdge(Edge edge) { + getGetStatementsOf(edge.getCallee()).forEach(getStmt -> + processGetStatementOnCallEdge(edge, getStmt)); + } + + /* + * [RelayEdge] rules, a relay PFG edge: --> should be converted to --> + * @param edge: --> , edge is not terminal PFG + */ + @Override + public void onNewPFGEdge(PointerFlowEdge edge) { + Pointer source = edge.source(), target = edge.target(); + FlowKind kind = edge.kind(); + // m_ret \in cutReturns, --> is a relay PFG edge + if (target instanceof CSVar csVar && cutReturnVars.contains(csVar.getVar()) && kind != FlowKind.NON_RELAY_GET) { + CSMethod csMethod = csManager.getCSMethod(csVar.getContext(), csVar.getVar().getMethod()); // c + // \forall --> , add --> + callGraph.getCallersOf(csMethod).forEach(csCallSite -> { + Var lhs = csCallSite.getCallSite().getLValue(); // r + if (lhs != null && isConcerned(lhs)) { + CSVar csLHS = csManager.getCSVar(csCallSite.getContext(), lhs); + solver.addPFGEdge(source, csLHS, kind, edge.getTransfers()); // --> + } + }); + } + } + + /* + * [RelayEdge]: --> translate to --> + * @param csMethod: , + * @param ret: m_ret \in cutReturns + */ + private void addCutReturnVar(CSMethod csMethod, Var ret) { + cutReturnVars.add(ret); + solver.addCutReturnVar(ret); + CSVar csRet = csManager.getCSVar(csMethod.getContext(), ret); // + csRet.getInEdges().forEach(edge -> { // --> + if (edge.kind() != FlowKind.NON_RELAY_GET) { + // --> + callGraph.getCallersOf(csMethod).forEach(csCallSite -> { + Var lhs = csCallSite.getCallSite().getLValue(); // r + if (lhs != null && isConcerned(lhs)) { + CSVar csLHS = csManager.getCSVar(csCallSite.getContext(), lhs); // + solver.addPFGEdge(edge.source(), csLHS, edge.kind(), edge.getTransfers()); // --> + } + }); + } + }); + } + + private boolean addSetStatement(CSMethod csMethod, SetStatement setStatement) { + return setStatements.put(csMethod, setStatement); + } + + private boolean addGetStatement(CSMethod csMethod, GetStatement getStmt) { + return getStatements.put(csMethod, getStmt); + } + + private Set getSetStatementsOf(CSMethod csMethod) { // 得到一个方法里所有的SetStatement + return setStatements.get(csMethod); + } + + private Set getGetStatementsOf(CSMethod csMethod) { + return getStatements.get(csMethod); + } + + + private List getAbstractLoadFields(Var base) { + return abstractLoadFields.computeIfAbsent(base, v -> new ArrayList<>()); + } + + private List getAbstractStoreFields(Var base) { + return abstractStoreFields.computeIfAbsent(base, v -> new ArrayList<>()); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/GetStatement.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/GetStatement.java new file mode 100644 index 000000000..210ea3181 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/GetStatement.java @@ -0,0 +1,10 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.ir.proginfo.FieldRef; + +public record GetStatement(int lhsIndex, ParameterIndex baseIndex, FieldRef fieldRef) { + @Override + public String toString() { + return "[GetStmt]" + lhsIndex + "=" + baseIndex().toString() + "." + fieldRef.getName(); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/ParameterIndex.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/ParameterIndex.java new file mode 100644 index 000000000..088e85cf8 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/ParameterIndex.java @@ -0,0 +1,70 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.analysis.graph.callgraph.Edge; +import pascal.taie.analysis.pta.core.cs.element.CSCallSite; +import pascal.taie.analysis.pta.core.cs.element.CSMethod; +import pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty; +import pascal.taie.analysis.pta.plugin.reflection.ReflectiveCallEdge; +import pascal.taie.ir.IR; +import pascal.taie.ir.exp.InvokeExp; +import pascal.taie.ir.exp.InvokeInstanceExp; +import pascal.taie.ir.exp.InvokeSpecial; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.Invoke; +import pascal.taie.util.AnalysisException; +import pascal.taie.util.collection.Maps; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +public record ParameterIndex(boolean isThis, int index) { + public static ParameterIndex THISINDEX = new ParameterIndex(true, 0); + + public static Map realParameters = Maps.newMap(); + + @Override + public String toString() { + return isThis ? "%this" : index + "@parameter"; + } + + public static ParameterIndex getRealParameterIndex(int index) { + return realParameters.computeIfAbsent(index, i -> new ParameterIndex(false, index)); + } + + public static int GetReturnVariableIndex(Var var) { // -1: 不是return Variable, 0, ... : index + // TODO: check definitions of return variables + if (var.getMethod().isAbstract()) + return -1; + IR methodIR = var.getMethod().getIR(); + List returnVars = methodIR.getReturnVars(); + int len = returnVars.size(), i; + for (i = 0; i < len; i ++) + if (returnVars.get(i).equals(var)) + return i; + return -1; + } + + @Nullable + public static Var getCorrespondingArgument(Edge edge, ParameterIndex parameterIndex) { + Invoke invoke = edge.getCallSite().getCallSite(); + InvokeExp invokeExp = invoke.getInvokeExp(); + if (edge instanceof ReflectiveCallEdge reflEdge) { + if (parameterIndex.isThis()) { + switch (ReflectiveEdgeProperty.getReflectiveKind(reflEdge)) { + case NEW_INSTANCE -> { return invoke.getResult(); } + case METHOD_INVOKE -> { return invokeExp.getArg(0); } + default -> throw new AnalysisException("Invalid Reflective Call Edge"); + } + } + else + return ReflectiveEdgeProperty.getVirtualArg(reflEdge); + } + if (!parameterIndex.isThis()) + return invokeExp.getArg(parameterIndex.index()); + else if (invokeExp instanceof InvokeInstanceExp instanceExp) + return instanceExp.getBase(); + + return null; + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/SetStatement.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/SetStatement.java new file mode 100644 index 000000000..54ab7fe52 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/field/SetStatement.java @@ -0,0 +1,25 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.field; + +import pascal.taie.ir.proginfo.FieldRef; + +import java.util.Objects; + +/** + * {@link SetStatement}表示需要向前去寻找callSite,并把这种抽象的Set行为作用到callSite处的指针(参数)的语句 + * 和之前的SetStmt不同,现在的{@link SetStatement}并不一定是简单的{@link pascal.taie.ir.stmt.StoreField}语句 + * 举个例子,如果Set方法内包含两层调用callSite(outer) -> set (包含callSite-inner) -> doSet方法 + * 那么在doSet方法内部存在一个{@link SetStatement},即为简单的{@link pascal.taie.ir.stmt.StoreField}语句 + * 但是当我们从这个{@link SetStatement}出发,找到了set方法中的callSite-inner后,我们发现这个{@link SetStatement}抽象到callSite-inner后的base和rhs + * 仍然是**set**方法中的parameter,于是callSite-inner也被抽象成一个{@link SetStatement},继续类似的操作(寻找callSite), + * 直到找到一个callSite,使得对应的base和rhs不再是方法的参数,此时建立**abstractStoreField,而不是{@link SetStatement} + * 具体的逻辑在solver中体现 + * {@param baseIndex} 表示SetStatement的base变量对应的parameter index(因为他一定是方法的参数) + * {@param fieldRef} 该SetStatement对应的field + * {@param rhsIndex} 与baseIndex类似 + */ +public record SetStatement(ParameterIndex baseIndex, FieldRef fieldRef, ParameterIndex rhsIndex) { + @Override + public String toString() { + return "[SetStmt]" + baseIndex().toString() + "." + fieldRef.getName() + " = " + rhsIndex.toString(); + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/LocalFlowHandler.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/LocalFlowHandler.java new file mode 100644 index 000000000..febec7692 --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/LocalFlowHandler.java @@ -0,0 +1,217 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.localflow; + +import pascal.taie.analysis.graph.callgraph.CallGraphs; +import pascal.taie.analysis.graph.callgraph.Edge; +import pascal.taie.analysis.graph.flowgraph.FlowKind; +import pascal.taie.analysis.pta.core.cs.context.Context; +import pascal.taie.analysis.pta.core.cs.element.CSCallSite; +import pascal.taie.analysis.pta.core.cs.element.CSManager; +import pascal.taie.analysis.pta.core.cs.element.CSMethod; +import pascal.taie.analysis.pta.core.cs.element.CSVar; +import pascal.taie.analysis.pta.core.heap.HeapModel; +import pascal.taie.analysis.pta.core.heap.Obj; +import pascal.taie.analysis.pta.core.solver.CutShortcutSolver; +import pascal.taie.analysis.pta.core.solver.PointerFlowEdge; +import pascal.taie.analysis.pta.core.solver.Solver; +import pascal.taie.analysis.pta.plugin.Plugin; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex; +import pascal.taie.analysis.pta.pts.PointsToSet; +import pascal.taie.ir.exp.InvokeExp; +import pascal.taie.ir.exp.InvokeInstanceExp; +import pascal.taie.ir.exp.Var; +import pascal.taie.ir.stmt.*; +import pascal.taie.language.classes.JMethod; +import pascal.taie.util.AnalysisException; +import pascal.taie.util.collection.Maps; +import pascal.taie.util.collection.MultiMap; + +import java.util.List; + +import static pascal.taie.analysis.pta.core.solver.CutShortcutSolver.isConcerned; +import static pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex.*; +import static pascal.taie.analysis.pta.plugin.cutshortcut.localflow.ParameterIndexOrNewObj.INDEX_THIS; + +public class LocalFlowHandler implements Plugin { + private CutShortcutSolver solver; + + private CSManager csManager; + + private HeapModel heapModel; + + private int totIDMethod = 0; + + // method -> set of parameter indices or new objects that are directly reach return vars without indirect-value flow + private final MultiMap directlyReturnParams = Maps.newMultiMap(); + + public void setSolver(Solver solver) { + if (solver instanceof CutShortcutSolver cutShortcutSolver) { + this.solver = cutShortcutSolver; + csManager = solver.getCSManager(); + heapModel = solver.getHeapModel(); + } + else + throw new AnalysisException("Invalid solver"); + } + + /* + * process for callsite r = v.k(...) + */ + @Override + public void onNewPointsToSet(CSVar csVar, PointsToSet pts) { + Var base = csVar.getVar(); + Context varContext = csVar.getContext(); + // foreach r = v.k(...) + for (Invoke callSite : base.getInvokes()) { + Var lhs = callSite.getLValue(); // r + if (lhs != null && isConcerned(lhs)) { + CSVar csLHS = csManager.getCSVar(varContext, lhs); // + pts.forEach(recvObj -> { + // resolve v.k based on Type(o) where \in newPts(v) + JMethod callee = CallGraphs.resolveCallee( + recvObj.getObject().getType(), callSite); + // if v -> r holds for this callee, add to newPts(r) + if (callee != null && directlyReturnParams.get(callee).contains(INDEX_THIS)) + solver.addPointsTo(csLHS, recvObj); + }); + } + } + } + + @Override + public void onNewMethod(JMethod method) { + if (!method.isAbstract()) { + List retVars = method.getIR().getReturnVars(); + MultiMap result = getVariablesAssignedFromParameters(method); + for (Var ret: retVars) { + if (!result.get(ret).isEmpty()) { + totIDMethod++; + break; + } + } + result.forEach((var, index) -> { + if (retVars.contains(var)) { + solver.addSelectedMethod(method); + directlyReturnParams.put(method, index); + solver.addCutReturnVar(var); + } + }); + } + } + + private MultiMap getVariablesAssignedFromParameters(JMethod method) { + MultiMap result = Maps.newMultiMap(); + MultiMap definitions = Maps.newMultiMap(); + method.getIR().forEach(stmt -> stmt.getDef().ifPresent(def -> { + if (def instanceof Var varDef) + definitions.put(varDef, stmt); + })); + for (int i = 0; i < method.getParamCount(); i++) { + Var param = method.getIR().getParam(i); + if (isConcerned(param)) { // parameter which is not redefined in the method body + if (definitions.get(param).isEmpty() || definitions.get(param).stream().allMatch(stmt -> stmt instanceof New)) { + result.put(param, new ParameterIndexOrNewObj(false, getRealParameterIndex(i), null)); + definitions.get(param).forEach(stmt -> result.put(param, + new ParameterIndexOrNewObj(true, null, heapModel.getObj((New) stmt))) + ); + } + } + } + Var thisVar = method.getIR().getThis(); + if (thisVar != null) + result.put(thisVar, INDEX_THIS); + + boolean changed = true; + int size = result.size(); + while (size > 0 && changed) { + method.getIR().getVars().forEach(var -> { + boolean flag = true; + for (Stmt def: definitions.get(var)) { + if (def instanceof Copy copy) { + Var rhs = copy.getRValue(); + if (result.get(rhs).isEmpty()) { + flag = false; + break; + } + } + else if (def instanceof Cast cast) { + Var rhs = cast.getRValue().getValue(); + if (result.get(rhs).isEmpty()) { + flag = false; + break; + } + } + else if (!(def instanceof New)) { + flag = false; + break; + } + } + if (flag) { + for (Stmt def: definitions.get(var)) { + Var rhs; + if (def instanceof Copy copy) + rhs = copy.getRValue(); + else if (def instanceof Cast cast) + rhs = cast.getRValue().getValue(); + else if (def instanceof New stmt) { + Obj obj = heapModel.getObj(stmt); + result.put(var, new ParameterIndexOrNewObj(true, null, obj)); + continue; + } + else + throw new AnalysisException("Neither Copy not Cast: " + def + "!"); + result.get(rhs).forEach(index -> result.put(var, index)); + } + } + }); + changed = result.size() != size; + size = result.size(); + } + return result; + } + + /* + * process --> + */ + @Override + public void onNewCallEdge(Edge edge) { + CSMethod csCallee = edge.getCallee(); // + JMethod callee = csCallee.getMethod(); // m + Context calleeContext = csCallee.getContext(); // c' + CSCallSite csCallSite = edge.getCallSite(); // + Context callSiteContext = csCallSite.getContext(); // c + Invoke callSite = csCallSite.getCallSite(); // l + InvokeExp invokeExp = callSite.getInvokeExp(); // r = v.k(...) + Var lhs = callSite.getLValue(); // r + if (lhs != null && isConcerned(lhs)) { + CSVar csLHS = csManager.getCSVar(callSiteContext, lhs); // + directlyReturnParams.get(callee).forEach(indexOrObj -> { + // \in newPts() \for o allocated in m + if (indexOrObj.isObj()) + solver.addPointsTo(csLHS, csManager.getCSObj(calleeContext, indexOrObj.obj())); + else { + // in callee m, exist only direct-value flow between: this --> return. + ParameterIndex index = indexOrObj.index(); + if (index == THISINDEX && invokeExp instanceof InvokeInstanceExp instanceExp) { + CSVar csBase = csManager.getCSVar(callSiteContext, instanceExp.getBase()); // + solver.getPointsToSetOf(csBase).forEach(csObj -> { + JMethod realCallee = CallGraphs.resolveCallee(csObj.getObject().getType(), callSite); + // \in pts(), Type(o) match m, then add to newPts() + if (callee.equals(realCallee)) + solver.addPointsTo(csLHS, csObj); + }); + } + // exist direct-value flow between: parameter i --> return. + if (index != THISINDEX) { + assert index != null; + Var arg = getCorrespondingArgument(edge, index); // ai + // --> + if (arg != null && isConcerned(arg)) + solver.addPFGEdge(new PointerFlowEdge(FlowKind.ID, csManager.getCSVar(callSiteContext, arg), csLHS), + lhs.getType()); + } + } + + }); + } + } +} diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/ParameterIndexOrNewObj.java b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/ParameterIndexOrNewObj.java new file mode 100644 index 000000000..99f7632cc --- /dev/null +++ b/src/main/java/pascal/taie/analysis/pta/plugin/cutshortcut/localflow/ParameterIndexOrNewObj.java @@ -0,0 +1,16 @@ +package pascal.taie.analysis.pta.plugin.cutshortcut.localflow; + +import pascal.taie.analysis.pta.core.heap.Obj; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex; + +import static pascal.taie.analysis.pta.plugin.cutshortcut.field.ParameterIndex.THISINDEX; + +public record ParameterIndexOrNewObj(boolean isObj, ParameterIndex index, Obj obj) { + public static ParameterIndexOrNewObj INDEX_THIS = new ParameterIndexOrNewObj(false, THISINDEX, null); + + @Override + public String toString() { + return isObj ? obj.toString() : index.toString(); + } +} + diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveActionModel.java b/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveActionModel.java index da83b6371..e5811b0c2 100644 --- a/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveActionModel.java +++ b/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveActionModel.java @@ -36,6 +36,7 @@ import pascal.taie.analysis.pta.core.heap.Obj; import pascal.taie.analysis.pta.core.solver.PointerFlowEdge; import pascal.taie.analysis.pta.core.solver.Solver; +import pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty; import pascal.taie.analysis.pta.plugin.util.AnalysisModelPlugin; import pascal.taie.analysis.pta.plugin.util.CSObjs; import pascal.taie.analysis.pta.plugin.util.InvokeHandler; @@ -53,6 +54,7 @@ import pascal.taie.language.type.VoidType; import pascal.taie.util.collection.Maps; import pascal.taie.util.collection.MultiMap; +import pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty; import javax.annotation.Nullable; import java.util.Set; @@ -61,6 +63,8 @@ import static pascal.taie.analysis.graph.flowgraph.FlowKind.PARAMETER_PASSING; import static pascal.taie.analysis.graph.flowgraph.FlowKind.RETURN; import static pascal.taie.analysis.graph.flowgraph.FlowKind.STATIC_STORE; +import static pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty.ReflectiveCallKind.METHOD_INVOKE; +import static pascal.taie.analysis.pta.plugin.cutshortcut.ReflectiveEdgeProperty.ReflectiveCallKind.NEW_INSTANCE; import static pascal.taie.analysis.pta.plugin.util.InvokeUtils.BASE; /** @@ -112,6 +116,7 @@ public class ReflectiveActionModel extends AnalysisModelPlugin { this.invokesWithLog = invokesWithLog; } + // reflection type: r = c.newInstance(args) @InvokeHandler(signature = "", argIndexes = {BASE}) public void classNewInstance(Context context, Invoke invoke, PointsToSet classes) { classes.forEach(obj -> { @@ -124,12 +129,13 @@ public void classNewInstance(Context context, Invoke invoke, PointsToSet classes if (init != null && !typeMatcher.isUnmatched(invoke, init)) { ClassType type = clazz.getType(); CSObj csNewObj = newReflectiveObj(context, invoke, type); - addReflectiveCallEdge(context, invoke, csNewObj, init, null); + addReflectiveCallEdge(context, invoke, csNewObj, init, null, NEW_INSTANCE); } } }); } + // reflection type: r = c.newInstance(args) @InvokeHandler(signature = "", argIndexes = {BASE}) public void constructorNewInstance(Context context, Invoke invoke, PointsToSet constructors) { constructors.forEach(obj -> { @@ -141,7 +147,7 @@ public void constructorNewInstance(Context context, Invoke invoke, PointsToSet c ClassType type = constructor.getDeclaringClass().getType(); CSObj csNewObj = newReflectiveObj(context, invoke, type); addReflectiveCallEdge(context, invoke, csNewObj, - constructor, invoke.getInvokeExp().getArg(0)); + constructor, invoke.getInvokeExp().getArg(0), NEW_INSTANCE); } }); } @@ -158,6 +164,7 @@ private CSObj newReflectiveObj(Context context, Invoke invoke, ReferenceType typ return csNewObj; } + // reflection type: r = m.invoke(obj, args) @InvokeHandler(signature = "", argIndexes = {BASE, 0}) public void methodInvoke(Context context, Invoke invoke, PointsToSet mtdObjs, PointsToSet recvObjs) { @@ -168,12 +175,11 @@ public void methodInvoke(Context context, Invoke invoke, } JMethod target = CSObjs.toMethod(mtdObj); if (target != null && !typeMatcher.isUnmatched(invoke, target)) { - if (target.isStatic()) { - addReflectiveCallEdge(context, invoke, null, target, argsVar); - } else { + if (target.isStatic()) + addReflectiveCallEdge(context, invoke, null, target, argsVar, METHOD_INVOKE); + else recvObjs.forEach(recvObj -> - addReflectiveCallEdge(context, invoke, recvObj, target, argsVar)); - } + addReflectiveCallEdge(context, invoke, recvObj, target, argsVar, METHOD_INVOKE)); } }); } @@ -272,7 +278,7 @@ private boolean isInvalidTarget(Invoke invoke, CSObj metaObj) { private void addReflectiveCallEdge( Context callerCtx, Invoke callSite, - @Nullable CSObj recvObj, JMethod callee, Var args) { + @Nullable CSObj recvObj, JMethod callee, Var args, ReflectiveEdgeProperty.ReflectiveCallKind kind) { if (!callee.isConstructor() && !callee.isStatic()) { // dispatch for instance method (except constructor) assert recvObj != null : "recvObj is required for instance method"; @@ -284,15 +290,16 @@ private void addReflectiveCallEdge( } CSCallSite csCallSite = csManager.getCSCallSite(callerCtx, callSite); Context calleeCtx; - if (callee.isStatic()) { + if (callee.isStatic()) calleeCtx = selector.selectContext(csCallSite, callee); - } else { + else { calleeCtx = selector.selectContext(csCallSite, recvObj, callee); // pass receiver object to 'this' variable of callee solver.addVarPointsTo(calleeCtx, callee.getIR().getThis(), recvObj); } ReflectiveCallEdge callEdge = new ReflectiveCallEdge(csCallSite, csManager.getCSMethod(calleeCtx, callee), args); + ReflectiveEdgeProperty.setReflectiveKind(callEdge, kind); solver.addCallEdge(callEdge); allTargets.put(callSite, callee); // record target } diff --git a/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveCallEdge.java b/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveCallEdge.java index 423751912..c95994058 100644 --- a/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveCallEdge.java +++ b/src/main/java/pascal/taie/analysis/pta/plugin/reflection/ReflectiveCallEdge.java @@ -32,8 +32,7 @@ /** * Represents reflective call edges. */ -class ReflectiveCallEdge extends OtherEdge { - +public class ReflectiveCallEdge extends OtherEdge { /** * Variable pointing to the array argument of reflective call, * which contains the arguments for the reflective target method, i.e., @@ -49,7 +48,7 @@ class ReflectiveCallEdge extends OtherEdge { } @Nullable - Var getArgs() { + public Var getArgs() { return args; } } diff --git a/src/main/java/pascal/taie/config/Options.java b/src/main/java/pascal/taie/config/Options.java index add8a63b7..fe8d49755 100644 --- a/src/main/java/pascal/taie/config/Options.java +++ b/src/main/java/pascal/taie/config/Options.java @@ -162,6 +162,14 @@ public boolean isPrependJVM() { return prependJVM; } + @JsonProperty + @Option(names = {"-lj", "--lib-jre"}, + description = "JRE library path, (default: ${DEFAULT-VALUE})", + defaultValue = "java-benchmarks/JREs") + private String libJREPath; + + public String getLibJREPath() { return libJREPath; } + @JsonProperty @Option(names = {"-ap", "--allow-phantom"}, description = "Allow Tai-e to process phantom references, i.e.," + diff --git a/src/main/java/pascal/taie/ir/exp/Var.java b/src/main/java/pascal/taie/ir/exp/Var.java index 403ce2f53..9bb0b0454 100644 --- a/src/main/java/pascal/taie/ir/exp/Var.java +++ b/src/main/java/pascal/taie/ir/exp/Var.java @@ -22,6 +22,8 @@ package pascal.taie.ir.exp; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.AbstractLoadField; +import pascal.taie.analysis.pta.plugin.cutshortcut.field.AbstractStoreField; import pascal.taie.ir.stmt.Invoke; import pascal.taie.ir.stmt.LoadArray; import pascal.taie.ir.stmt.LoadField; @@ -253,6 +255,8 @@ private void readObject(ObjectInputStream s) throws IOException, * load array: x = v[i]; * store array: v[i] = x; * invocation: v.f(); + * abstract load field: x = absv.f; (for Cut-Shortcut analysis) + * abstract store field: absv.f = x; (for Cut-Shortcut analysis) * We use a separate class to store these relevant statements * (instead of directly storing them in {@link Var}) for saving space. * Most variables do not have any relevant statements, so these variables diff --git a/src/main/java/pascal/taie/ir/stmt/FieldStmt.java b/src/main/java/pascal/taie/ir/stmt/FieldStmt.java index 260054c43..0f7b5aa6a 100644 --- a/src/main/java/pascal/taie/ir/stmt/FieldStmt.java +++ b/src/main/java/pascal/taie/ir/stmt/FieldStmt.java @@ -32,7 +32,7 @@ */ public abstract class FieldStmt extends AssignStmt { - FieldStmt(L lvalue, R rvalue) { + protected FieldStmt(L lvalue, R rvalue) { super(lvalue, rvalue); } diff --git a/src/main/java/pascal/taie/util/collection/Triplet.java b/src/main/java/pascal/taie/util/collection/Triplet.java new file mode 100644 index 000000000..9c6b96bb8 --- /dev/null +++ b/src/main/java/pascal/taie/util/collection/Triplet.java @@ -0,0 +1,11 @@ +package pascal.taie.util.collection; + +import java.io.Serializable; + +public record Triplet (T1 first, T2 second, T3 third) + implements Serializable { + @Override + public String toString() { + return "<" + first + ", " + second + ", " + third + ">"; + } +} diff --git a/src/main/resources/container-config/array-initializer.yml b/src/main/resources/container-config/array-initializer.yml new file mode 100644 index 000000000..7038239a3 --- /dev/null +++ b/src/main/resources/container-config/array-initializer.yml @@ -0,0 +1,4 @@ +ArrayInit: + - {"signature": "(java.lang.Object[])>", "src": 0, "dst": -1} + - {"signature": "", "src": 1, "dst": 0} + - {"signature": "(java.lang.Object[])>", "src": 0, "dst": -1} diff --git a/src/main/resources/container-config/entrance-append.yml b/src/main/resources/container-config/entrance-append.yml new file mode 100644 index 000000000..eb640b9e2 --- /dev/null +++ b/src/main/resources/container-config/entrance-append.yml @@ -0,0 +1,159 @@ +MapValue: + # HashMap, LinkedHashMap, TreeMap, Hashtable, WeakHashMap, IdentityHashMap, EnumMap + # ConcurrentHashMap, ConcurrentSkipListMap, Properties + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 2} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 2} + + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '(java.lang.Object,java.lang.Object)>', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + +MapKey: + # HashMap, LinkedHashMap, TreeMap, Hashtable, WeakHashMap, IdentityHashMap, EnumMap + # ConcurrentHashMap, ConcurrentSkipListMap, Properties + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '(java.lang.Object,java.lang.Object)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + +ColValue: + # ArrayList, LinkedList, Vector, ArrayDeque, Stack, AbstractQueue, PriorityQueue, TreeSet + # SynchronousQueue, ConcurrentLinkedQueue, DelayQueue, ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue + # ConcurrentSkipListSet, CopyOnWriteArrayList + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 0} + + # those collection variables returned by Collection.xx(..), Arrays.asList(..). UnmodifiedCollection,List do not support entrance + - {signature: '(int,java.lang.Object)>', index: 1} + - {signature: '(java.lang.Object)>', index: 0} + - {signature: '(java.lang.Object)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 1} + + # Arrays.asList() 返回的内部类 (固定大小列表,set可用,add会抛异常但需建模) + - {signature: '', index: 1} + # ArrayList.subList()返回的内部类 + - {signature: '', index: 1} + - {signature: '', index: 1} + # CopyOnWriteArrayList.subList()返回的内部类 + - {signature: '', index: 1} + - {signature: '', index: 1} + # ScheduledThreadPoolExecutor内部类 + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + diff --git a/src/main/resources/container-config/entrance-extend.yml b/src/main/resources/container-config/entrance-extend.yml new file mode 100644 index 000000000..374d0bdf2 --- /dev/null +++ b/src/main/resources/container-config/entrance-extend.yml @@ -0,0 +1,133 @@ +MapToMap: + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '(java.util.SortedMap)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '(java.util.EnumMap)>', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Properties)>', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Map)>', index: 0} + - {signature: '(java.util.SortedMap)>', index: 0} + - {signature: '', index: 0} + + # called in Collections.synchronizedMap(...) + - {signature: '(java.util.Map)>', index: 0} + # called in Collections.unmodifiableMap(...) + - {signature: '(java.util.Map)>', index: 0} + - {signature: '', index: 0} + # called in Collections.unmodifiableSortedMap(...) + - {signature: '(java.util.SortedMap)>', index: 0} + - {signature: '', index: 0} + # called in Collections.checkedMap(...) + - {signature: '', index: 0} + - {signature: '(java.util.Map,java.lang.Class,java.lang.Class)>', index: 0} + # called in Collections.checkedSortedMap(...) + - {signature: '(java.util.SortedMap,java.lang.Class,java.lang.Class)>', index: 0} + + # TreeMap subView + - {signature: '(java.util.TreeMap,boolean,java.lang.Object,boolean,boolean,java.lang.Object,boolean)>', index: 0} + - {signature: '(java.util.TreeMap,boolean,java.lang.Object,boolean,boolean,java.lang.Object,boolean)>', index: 0} + - {signature: '(java.util.TreeMap,boolean,java.lang.Object,boolean,boolean,java.lang.Object,boolean)>', index: 0} + - {signature: '(java.util.concurrent.ConcurrentSkipListMap,java.lang.Object,boolean,java.lang.Object,boolean,boolean)>', index: 0} + +ColToCol: + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.SortedSet)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.PriorityQueue)>', index: 0} + - {signature: '(java.util.SortedSet)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.AbstractList,int,int)>', index: 0} + - {signature: '', index: 1} + - {signature: '(java.util.AbstractList,int,int)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.SortedSet)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '', index: 0} + + # inner class + - {signature: '(java.util.ArrayList,java.util.AbstractList,int,int,int)>', index: 1} + - {signature: '', index: 1} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + - {signature: '(java.util.concurrent.CopyOnWriteArrayList,int,int)>', index: 0} + - {signature: '(java.util.Collection)>', index: 0} + - {signature: '(java.util.Collection,java.lang.Class)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Set)>', index: 0} + - {signature: '(java.util.SortedSet)>', index: 0} + - {signature: '(java.util.SortedSet,java.lang.Object)>', index: 0} + - {signature: '(java.util.Deque)>', index: 0} + - {signature: '(java.util.Set,java.lang.Class)>', index: 0} + - {signature: '(java.util.Collection,java.lang.Class)>', index: 0} + - {signature: '', index: 0} + - {signature: '(java.util.Collection,java.lang.Object)>', index: 0} + - {signature: '(java.util.List,java.lang.Class)>', index: 0} + - {signature: '(java.util.Set)>', index: 0} + - {signature: '(java.util.Set,java.lang.Object)>', index: 0} + - {signature: '(java.util.List)>', index: 0} + - {signature: '(java.util.List,java.lang.Object)>', index: 0} + - {signature: '(java.util.List)>', index: 0} + - {signature: '(java.util.List)>', index: 0} + - {signature: '(java.util.List,java.lang.Object)>', index: 0} + - {signature: '(java.util.Collections$1)>', index: 0} + - {signature: '(java.util.SortedSet)>', index: 0} + - {signature: '(java.util.SortedSet,java.lang.Class)>', index: 0} + - {signature: '(java.util.List)>', index: 0} + - {signature: '(java.util.Set)>', index: 0} + - {signature: '(java.util.List,java.lang.Class)>', index: 0} + - {signature: '', index: 0} + - {signature: '', index: 1} + - {signature: '', index: 1} + +MapKeySetToCol: + - {signature: '(java.util.Map)>', index: 0} + - # Although ConcurrentSkipListSet is collection, its iterator return ConcurrentNavigableMap.keyIter(), hence for ptsH, consider it as a map. + - {signature: '(java.util.concurrent.ConcurrentNavigableMap)>', index: 0} diff --git a/src/main/resources/container-config/exit.yml b/src/main/resources/container-config/exit.yml new file mode 100644 index 000000000..4234f670b --- /dev/null +++ b/src/main/resources/container-config/exit.yml @@ -0,0 +1,342 @@ +ColValue: + # ArrayList, LinkedList, Vector, ArrayDeque, Stack, SubList, PriorityQueue, TreeSet + # SynchronousQueue, ConcurrentLinkedQueue, DelayQueue, ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue + # ConcurrentSkipListSet, CopyOnWriteArrayList + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + +MapValue: + # HashMap, LinkedHashMap, TreeMap, Hashtable, WeakHashMap, IdentityHashMap, EnumMap + # ConcurrentHashMap, ConcurrentSkipListMap, Properties + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + # ===== 漏掉的 ConcurrentSkipListMap 子视图操作 ===== + - '' + - '' + - '' + - '' + - '' + + +MapKey: + # TreeMap, ConcurrentSkipListMap + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + # inner class + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + +# ArrayList, LinkedList, Vector, ArrayDeque, Stack, SubList, PriorityQueue, TreeSet +# SynchronousQueue, ConcurrentLinkedQueue, DelayQueue, ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue +# ConcurrentSkipListSet, CopyOnWriteArrayList +# 注意不要包括Map.entrySet.Iterator.next方法,这一类为Transfer而不是Exit, +# Set的iterator返回的可能是Map.keyIterator(),因此如果r = v.next()调用Map.keyIterator(),如果v是collection,那么r是Col-Value;如果是Map.keySet,那么是 +ColIter: + - '' # AbstractList.iterator() + - '' # AbstractList.listIterator() + - '' # ArrayList.iterator() + - '' # ArrayList.listIterator() + - '' # ArrayList.SubList.listIterator(), anymous class + - '' + - '' # LinkedList.iterator()/listIterator() + - '' + - '' # LinkedList.descendingIterator() + - '' # SubList.listIterator(), anymous class + - '' + - '' # Vector.iterator() + - '' # Vector.listIterator() + - '' # Vector.elements() + - '' # PriorityDeque.iterator() + - '' # ArrayDeque.iterator() + - '' # ArrayDeque.descendingIterator() + - '' # AbstractMap.keySet().iterator() + - '' # AbstractMap.values().iterator() + - '' # HashMap.values().iterator() + - '' # HashMap.keySet().iterator()/HashSet.iterator() + - '' # HashMap.values().iterator() + - '' # WeakHashMap.keySet().iterator() + - '' # WeakHashMap.values().iterator() + - '' # IdentityHashMap.keySet().iterator() + - '' # IdentityHashMap.values().iterator() + - '' # EnumMap.keySet().iterator() + - '' # EnumMap.values().iterator() + - '' # TreeMap.keySet().iterator()/TreeSet.iterator() + - '' # TreeMap.keySet().descendingIterator()/TreeSet.descendingIterator() + - '' # TreeMap.values().iterator() + - '' # TreeMap.keySet().iterator()/TreeSet.iterator() + - '' # TreeMap.keySet().descendingIterator()/TreeSet.descendingIterator() + # Hashtable.keys() => MAP_KEY_ITR, keySet() => MAP_KEY_SET, elements() => MAP_VALUE_ITR, values() => MAP_VALUES + - '' # HashTable.keySet()/values().iterator() + - '' + # ConcurrentHashMap.keys() => MAP_KEY_ITR, keySet() => MAP_KEY_SET, elements() => MAP_VALUE_ITR, values() => MAP_VALUES + - '' # ConcurrentHashMap.keySet().iterator() + - '' + - '' # ConcurrentHashMap.values().iterator() + - '' + # ConcurrentSkipListMap + - '' # ConcurrentSkipListMap.keySet().iterator() + - '' # ConcurrentSkipListMap.values().iterator() + - '' # ConcurrentSkipListMap.subMap().keySet().iterator() + - '' # ConcurrentSkipListMap.subMap().values().iterator() + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' # Collections.unmodifiableCollection().iterator() + - '' # Collections.unmodifiableList().iterator() + - '' # Collections.checkedCollection().iterator() + - '' # Collections.checkedList().listIterator() + - '' # Collections.singletonSet()/singletonList().listIterator() + - '' # HashMap.entrySet().iterator() + - '' # WeakHashMap.entrySet().iterator() + - '' # IdentityHashMap.entrySet().iterator() + - '' # TreeMap.subMap().entrySet().iterator() + - '' # TreeMap.descendingMap().entrySet().iterator() + - '' # EnumMap.entrySet().iterator() + - '' # TreeMap.entrySet().iterator() + +#MapGetKey: +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# +#MapGetValue: +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' +# - '' diff --git a/src/main/resources/container-config/host-classes.yml b/src/main/resources/container-config/host-classes.yml new file mode 100644 index 000000000..113d7d8ec --- /dev/null +++ b/src/main/resources/container-config/host-classes.yml @@ -0,0 +1,121 @@ +Col: + - "java.util.ArrayList" + - "java.util.LinkedList" + - "java.util.SubList" + - "java.util.RandomAccessSubList" + - "java.util.Vector" + - "java.util.Stack" + - "java.util.PriorityQueue" + - "java.util.ArrayDeque" + - "java.util.HashSet" + - "java.util.LinkedHashSet" + - "java.util.TreeSet" + - "java.util.concurrent.ConcurrentLinkedQueue" + - "java.util.concurrent.ArrayBlockingQueue" + - "java.util.concurrent.LinkedBlockingQueue" + - "java.util.concurrent.LinkedBlockingDeque" + - "java.util.concurrent.PriorityBlockingQueue" + - "java.util.concurrent.DelayQueue" + - "java.util.concurrent.SynchronousQueue" + - "java.util.concurrent.ConcurrentSkipListSet" + - "java.util.concurrent.CopyOnWriteArrayList" + - "java.util.concurrent.CopyOnWriteArraySet" + + # inner class + - "java.util.Arrays$ArrayList" + - "java.util.ArrayList$SubList" + - "java.util.concurrent.CopyOnWriteArrayList$COWSubList" + - "java.util.Collections$UnmodifiableCollection" + - "java.util.Collections$UnmodifiableSet" + - "java.util.Collections$UnmodifiableSortedSet" + - "java.util.Collections$CopiesList" + - "java.util.Collections$AsLIFOQueue" + - "java.util.Collections$SynchronizedCollection" + - "java.util.Collections$UnmodifiableList" + - "java.util.Collections$CheckedCollection" + - "java.util.Collections$EmptySet" + - "java.util.Collections$SetFromMap" + - "java.util.Collections$SynchronizedList" + - "java.util.Collections$SynchronizedRandomAccessList" + - "java.util.Collections$SynchronizedSet" + - "java.util.Collections$CheckedList" + - "java.util.Collections$CheckedRandomAccessList" + - "java.util.Collections$UnmodifiableRandomAccessList" + - "java.util.Collections$SingletonSet" + - "java.util.Collections$SingletonList" + - "java.util.Collections$CheckedSet" + - "java.util.Collections$EmptyList" + - "java.util.Collections$CheckedSortedSet" + - "java.util.Collections$SynchronizedSortedSet" + - "java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue" + +Map: + - "java.util.HashMap" + - "java.util.LinkedHashMap" + - "java.util.IdentityHashMap" + - "java.util.TreeMap" + - "java.util.AbstractMap" + - "java.util.Hashtable" + - "java.util.WeakHashMap" + - "java.util.EnumMap" + - "java.util.Properties" + + - "java.util.concurrent.ConcurrentHashMap" + - "java.util.concurrent.ConcurrentSkipListMap" + - "java.util.Collections$SynchronizedMap" + - "java.util.Collections$SynchronizedSortedMap" + - "java.util.Collections$EmptyMap" + - "java.util.Collections$UnmodifiableMap" + - "java.util.Collections$UnmodifiableSortedMap" + - "java.util.Collections$SingletonMap" + - "java.util.Collections$CheckedMap" + - "java.util.Collections$CheckedSortedMap" + + - "java.util.TreeMap$NavigableSubMap" + - "java.util.TreeMap$DescendingSubMap" + - "java.util.TreeMap$AscendingSubMap" + - "java.util.TreeMap$SubMap" + +EntrySet: + - "java.util.HashMap$EntrySet" + - "java.util.LinkedHashMap$LinkedEntrySet" + - "java.util.TreeMap$EntrySet" + - "java.util.EnumMap$EntrySet" + - "java.util.IdentityHashMap$EntrySet" + - "java.util.WeakHashMap$EntrySet" + - "java.util.Hashtable$EntrySet" + - "java.util.concurrent.ConcurrentHashMap$EntrySetView" + - "java.util.concurrent.ConcurrentSkipListMap$EntrySet" + + - "java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet" + - "java.util.Collections$CheckedMap$CheckedEntrySet" + - "java.util.TreeMap$NavigableSubMap$EntrySetView" + - "java.util.TreeMap$DescendingSubMap$DescendingEntrySetView" + - "java.util.TreeMap$AscendingSubMap$AscendingEntrySetView" + +KeySet: + - "java.util.HashMap$KeySet" + - "java.util.LinkedHashMap$LinkedKeySet" + - "java.util.IdentityHashMap$KeySet" + - "java.util.WeakHashMap$KeySet" + - "java.util.Hashtable$KeySet" + - "java.util.AbstractMap$1" # AbstractMap.keySet() is anonymous class + - "java.util.TreeMap$KeySet" + - "java.util.EnumMap$KeySet" + - "java.util.concurrent.ConcurrentHashMap$KeySetView" + - "java.util.concurrent.ConcurrentSkipListMap$KeySet" + + +Values: + - "java.util.HashMap$Values" + - "java.util.LinkedHashMap$LinkedValues" + - "java.util.IdentityHashMap$Values" + - "java.util.WeakHashMap$Values" + - "java.util.Hashtable$ValueCollection" + - "java.util.AbstractMap$2" # AbstractMap.values() is anonymous class + - "java.util.TreeMap$Values2" + - "java.util.EnumMap$Values" + - "java.util.concurrent.ConcurrentHashMap$ValuesView" + - "java.util.concurrent.ConcurrentSkipListMap$Values" + + diff --git a/src/main/resources/tai-e-analyses.yml b/src/main/resources/tai-e-analyses.yml index 72e244ece..23943b9ae 100644 --- a/src/main/resources/tai-e-analyses.yml +++ b/src/main/resources/tai-e-analyses.yml @@ -3,6 +3,7 @@ id: pta options: cs: ci # | k-[obj|type|call][-k'h] + solver: default # | csc, which solver to use. Currently default or cut-shortcut only-app: false # only analyze application code implicit-entries: true # analyze implicit entries distinguish-string-constants: reflection # (distinguish reflection-relevant @@ -23,6 +24,7 @@ dump: false # whether dump points-to results (with contexts) dump-ci: false # whether dump points-to results (without contexts) dump-yaml: false # whether dump points-to results in yaml format + only-dump-app: false # only dump results of application code expected-file: null # path of expected file for comparing results reflection-inference: string-constant # | solar | null reflection-log: null # path to reflection log, required when reflection option is log