diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 97d80d6c..784f93cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,7 @@ jobs: fail-fast: false matrix: framework: [ Soot, SootUp, Opal ] + enableOnTheFlyCallGraph: [ false, true ] runs-on: ubuntu-latest steps: - name: Checkout source code @@ -26,4 +27,4 @@ jobs: java-package: jdk java-version: 11 - name: Build with Maven - run: mvn -B clean verify -DtestSetup=${{ matrix.framework }} + run: mvn -B clean verify -DtestSetup=${{ matrix.framework }} -DenableOnTheFlyCallGraph=${{ matrix.enableOnTheFlyCallGraph }} diff --git a/WPDS/src/main/java/wpds/impl/StackListener.java b/WPDS/src/main/java/wpds/impl/StackListener.java index 54246592..64602765 100644 --- a/WPDS/src/main/java/wpds/impl/StackListener.java +++ b/WPDS/src/main/java/wpds/impl/StackListener.java @@ -38,6 +38,11 @@ public StackListener(WeightedPAutomaton weightedPAutomaton, D state, N public void onOutTransitionAdded( Transition t, W w, WeightedPAutomaton weightedPAutomaton) { if (t.getLabel().equals(aut.epsilon())) return; + /* + * XXX: Does this always work, if the BoomerangOptions.onTheFlyCallGraph + * option is active? For now, the testcases seem to pass - reconsider this + * (that is, try to use an InitialStateListener). + */ if (this.aut.getInitialStates().contains(t.getTarget())) { if (t.getLabel().equals(source)) { anyContext(source); diff --git a/WPDS/src/main/java/wpds/impl/WeightedPAutomaton.java b/WPDS/src/main/java/wpds/impl/WeightedPAutomaton.java index 65a5780a..dc516449 100644 --- a/WPDS/src/main/java/wpds/impl/WeightedPAutomaton.java +++ b/WPDS/src/main/java/wpds/impl/WeightedPAutomaton.java @@ -17,7 +17,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Stopwatch; import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; @@ -42,6 +42,7 @@ import pathexpression.RegEx; import wpds.interfaces.ForwardDFSEpsilonVisitor; import wpds.interfaces.ForwardDFSVisitor; +import wpds.interfaces.InitialStateListener; import wpds.interfaces.ReachabilityListener; import wpds.interfaces.State; import wpds.interfaces.WPAStateListener; @@ -57,13 +58,14 @@ public abstract class WeightedPAutomaton> transitions = new LinkedHashSet<>(); // set F in paper [Reps2003] protected Set finalState = new LinkedHashSet<>(); - protected Multimap initialStatesToSource = HashMultimap.create(); + protected Multimap initialStatesToSource = LinkedHashMultimap.create(); // set P in paper [Reps2003] protected Set states = new LinkedHashSet<>(); - private final Multimap> transitionsOutOf = HashMultimap.create(); - private final Multimap> transitionsInto = HashMultimap.create(); + private final Multimap> transitionsOutOf = LinkedHashMultimap.create(); + private final Multimap> transitionsInto = LinkedHashMultimap.create(); private final Set> listeners = new LinkedHashSet<>(); - private final Multimap> stateListeners = HashMultimap.create(); + private final Set> initialStateListeners = new LinkedHashSet<>(); + private final Multimap> stateListeners = LinkedHashMultimap.create(); private final Map> stateToDFS = Maps.newHashMap(); private final Map> stateToEpsilonDFS = Maps.newHashMap(); private final Set> nestedAutomatons = new LinkedHashSet<>(); @@ -371,6 +373,15 @@ public void registerListener(WPAUpdateListener listener) { } } + public void registerListener(InitialStateListener listener) { + if (!initialStateListeners.add(listener)) { + return; + } + for (D initialState : Lists.newArrayList(getInitialStates())) { + listener.onInitialStateAdded(initialState); + } + } + private static int count = 0; private void increaseListenerCount(WPAStateListener l) { @@ -825,7 +836,13 @@ public boolean addUnbalancedState(D state, D parent) { } public boolean addInitialState(D state) { - return initialStatesToSource.put(state, state); + if (!initialStatesToSource.get(state).add(state)) { + return false; + } + for (InitialStateListener listener : Lists.newArrayList(initialStateListeners)) { + listener.onInitialStateAdded(state); + } + return true; } public void unregisterAllListeners() { diff --git a/WPDS/src/main/java/wpds/interfaces/InitialStateListener.java b/WPDS/src/main/java/wpds/interfaces/InitialStateListener.java new file mode 100644 index 00000000..4aea50ea --- /dev/null +++ b/WPDS/src/main/java/wpds/interfaces/InitialStateListener.java @@ -0,0 +1,19 @@ +/** + * ***************************************************************************** + * Copyright (c) 2018 Fraunhofer IEM, Paderborn, Germany + *

+ * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + *

+ * SPDX-License-Identifier: EPL-2.0 + *

+ * Contributors: + * Johannes Spaeth - initial API and implementation + * ***************************************************************************** + */ +package wpds.interfaces; + +public interface InitialStateListener { + void onInitialStateAdded(D initialState); +} diff --git a/boomerangPDS/src/main/java/boomerang/WeightedBoomerang.java b/boomerangPDS/src/main/java/boomerang/WeightedBoomerang.java index 91b4448e..017a710e 100644 --- a/boomerangPDS/src/main/java/boomerang/WeightedBoomerang.java +++ b/boomerangPDS/src/main/java/boomerang/WeightedBoomerang.java @@ -267,21 +267,24 @@ public void getPredecessor(Statement pred) { BackwardQuery bwq = BackwardQuery.make(new Edge(pred, stmt), stmt.getInvokeExpr().getArg(0)); backwardSolve(bwq); - for (ForwardQuery q : Lists.newArrayList(queryToSolvers.keySet())) { - if (queryToSolvers.get(q).getReachedStates().contains(bwq.asNode())) { - AllocVal v = q.getAllocVal(); - if (v.getAllocVal().isStringConstant()) { - String key = v.getAllocVal().getStringValue(); - backwardSolverIns.propagate( - node, - new PushNode<>( - new Edge(pred, stmt), - stmt.getInvokeExpr().getBase(), - StringBasedField.getInstance(key), - PDSSystem.FIELDS)); - } - } - } + WeightedBoomerang.this.registerSolverCreationListener( + (q, solver) -> + solver.registerListener( + n -> { + if (n.equals(bwq.asNode()) && q instanceof ForwardQuery) { + AllocVal v = ((ForwardQuery) q).getAllocVal(); + if (v.getAllocVal().isStringConstant()) { + String key = v.getAllocVal().getStringValue(); + backwardSolverIns.propagate( + node, + new PushNode<>( + new Edge(pred, stmt), + stmt.getInvokeExpr().getBase(), + StringBasedField.getInstance(key), + PDSSystem.FIELDS)); + } + } + })); } }); } @@ -296,21 +299,25 @@ public void getPredecessor(Statement pred) { BackwardQuery bwq = BackwardQuery.make(new Edge(pred, stmt), stmt.getInvokeExpr().getArg(0)); backwardSolve(bwq); - for (ForwardQuery q : Lists.newArrayList(queryToSolvers.keySet())) { - if (queryToSolvers.get(q).getReachedStates().contains(bwq.asNode())) { - AllocVal v = q.getAllocVal(); - - if (v.getAllocVal().isStringConstant()) { - String key = v.getAllocVal().getStringValue(); - NodeWithLocation succNode = - new NodeWithLocation<>( - new Edge(pred, stmt), - stmt.getInvokeExpr().getArg(1), - StringBasedField.getInstance(key)); - backwardSolverIns.propagate(node, new PopNode<>(succNode, PDSSystem.FIELDS)); - } - } - } + WeightedBoomerang.this.registerSolverCreationListener( + (q, solver) -> + solver.registerListener( + n -> { + if (n.equals(bwq.asNode()) && q instanceof ForwardQuery) { + AllocVal v = ((ForwardQuery) q).getAllocVal(); + + if (v.getAllocVal().isStringConstant()) { + String key = v.getAllocVal().getStringValue(); + NodeWithLocation succNode = + new NodeWithLocation<>( + new Edge(pred, stmt), + stmt.getInvokeExpr().getArg(1), + StringBasedField.getInstance(key)); + backwardSolverIns.propagate( + node, new PopNode<>(succNode, PDSSystem.FIELDS)); + } + } + })); } }); } @@ -325,27 +332,30 @@ protected void handleMapsForward(ForwardBoomerangSolver solver, Node succNode = - new NodeWithLocation<>( - new Edge(stmt, succ), - stmt.getLeftOp(), - StringBasedField.getInstance(key)); - solver.propagate(node, new PopNode<>(succNode, PDSSystem.FIELDS)); - } - } - } - } - }); + registerSolverCreationListener( + (q, solverForKeyArg) -> + solverForKeyArg.registerListener( + n -> + cfg.addSuccsOfListener( + new SuccessorListener(stmt) { + @Override + public void getSuccessor(Statement succ) { + if (n.equals(bwq.asNode()) && q instanceof ForwardQuery) { + AllocVal v = ((ForwardQuery) q).getAllocVal(); + + if (v.getAllocVal().isStringConstant()) { + String key = v.getAllocVal().getStringValue(); + NodeWithLocation succNode = + new NodeWithLocation<>( + new Edge(stmt, succ), + stmt.getLeftOp(), + StringBasedField.getInstance(key)); + solver.propagate( + node, new PopNode<>(succNode, PDSSystem.FIELDS)); + } + } + } + }))); } } if (stmt.getInvokeExpr().getDeclaredMethod().toMethodWrapper().equals(MAP_PUT_SIGNATURE)) { @@ -353,27 +363,29 @@ public void getSuccessor(Statement succ) { BackwardQuery bwq = BackwardQuery.make(node.stmt(), stmt.getInvokeExpr().getArg(0)); backwardSolve(bwq); - cfg.addSuccsOfListener( - new SuccessorListener(stmt) { - @Override - public void getSuccessor(Statement succ) { - for (ForwardQuery q : Lists.newArrayList(queryToSolvers.keySet())) { - if (queryToSolvers.get(q).getReachedStates().contains(bwq.asNode())) { - AllocVal v = q.getAllocVal(); - if (v.getAllocVal().isStringConstant()) { - String key = v.getAllocVal().getStringValue(); - solver.propagate( - node, - new PushNode<>( - new Edge(stmt, succ), - stmt.getInvokeExpr().getBase(), - StringBasedField.getInstance(key), - PDSSystem.FIELDS)); - } - } - } - } - }); + registerSolverCreationListener( + (q, solverForKeyArg) -> + solverForKeyArg.registerListener( + n -> + cfg.addSuccsOfListener( + new SuccessorListener(stmt) { + @Override + public void getSuccessor(Statement succ) { + if (n.equals(bwq.asNode()) && q instanceof ForwardQuery) { + AllocVal v = ((ForwardQuery) q).getAllocVal(); + if (v.getAllocVal().isStringConstant()) { + String key = v.getAllocVal().getStringValue(); + solver.propagate( + node, + new PushNode<>( + new Edge(stmt, succ), + stmt.getInvokeExpr().getBase(), + StringBasedField.getInstance(key), + PDSSystem.FIELDS)); + } + } + } + }))); } } } @@ -988,10 +1000,14 @@ public ForwardBoomerangResults solve(ForwardQuery query) { } public BackwardBoomerangResults solve(BackwardQuery query) { - return solve(query, true); + return solve(query, true, true); } public BackwardBoomerangResults solve(BackwardQuery query, boolean timing) { + return solve(query, timing, true); + } + + public BackwardBoomerangResults solve(BackwardQuery query, boolean timing, boolean fallback) { if (!options.allowMultipleQueries() && solving) { throw new RuntimeException( "One cannot re-use the same Boomerang solver for more than one query, unless option allowMultipleQueries is enabled. If allowMultipleQueries is enabled, ensure to call unregisterAllListeners() on this instance upon termination of all queries."); @@ -1005,6 +1021,9 @@ public BackwardBoomerangResults solve(BackwardQuery query, boolean timing) { queryGraph.addRoot(query); LOGGER.trace("Starting backward analysis of: {}", query); backwardSolve(query); + if (fallback) { + icfg.computeFallback(); + } } catch (BoomerangTimeoutException e) { timedout = true; LOGGER.trace("Timeout ({}) of query: {} ", analysisWatch, query); @@ -1037,6 +1056,7 @@ public BackwardBoomerangResults solveUnderScope( LOGGER.trace("Starting backward analysis of: {}", query); backwardSolve(query); queryGraph.addEdge(parentQuery, triggeringNode, query); + icfg.computeFallback(); this.debugOutput(); } catch (BoomerangTimeoutException e) { timedout = true; @@ -1064,6 +1084,7 @@ public ForwardBoomerangResults solveUnderScope( LOGGER.trace("Starting forward analysis of: {}", query); forwardSolve(query); queryGraph.addEdge(parentQuery, triggeringNode, query); + icfg.computeFallback(); LOGGER.trace( "Query terminated in {} ({}), visited methods {}", analysisWatch, diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/BackwardsObservableICFG.java b/boomerangPDS/src/main/java/boomerang/callgraph/BackwardsObservableICFG.java index 2677e994..f4e922b6 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/BackwardsObservableICFG.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/BackwardsObservableICFG.java @@ -77,7 +77,7 @@ public void computeFallback() { } @Override - public void addEdges(Edge e) { - this.delegate.addEdges(e); + public void addEdge(Edge e) { + this.delegate.addEdge(e); } } diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/BoomerangResolver.java b/boomerangPDS/src/main/java/boomerang/callgraph/BoomerangResolver.java index e8a835c9..9991d82e 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/BoomerangResolver.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/BoomerangResolver.java @@ -15,7 +15,6 @@ package boomerang.callgraph; import boomerang.BackwardQuery; -import boomerang.Boomerang; import boomerang.ForwardQuery; import boomerang.Query; import boomerang.SolverCreationListener; @@ -24,7 +23,6 @@ import boomerang.scope.CallGraph; import boomerang.scope.ControlFlowGraph.Edge; import boomerang.scope.DeclaredMethod; -import boomerang.scope.FrameworkScope; import boomerang.scope.InvokeExpr; import boomerang.scope.Method; import boomerang.scope.Statement; @@ -34,17 +32,17 @@ import boomerang.solver.AbstractBoomerangSolver; import boomerang.solver.ForwardBoomerangSolver; import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sync.pds.solver.nodes.INode; -import sync.pds.solver.nodes.Node; import wpds.impl.Weight; public class BoomerangResolver implements ICallerCalleeResolutionStrategy { @@ -62,116 +60,117 @@ public enum NoCalleeFoundFallbackOptions { private static final String THREAD_RUN_SUB_SIGNATURE = "void run()"; private static final NoCalleeFoundFallbackOptions FALLBACK_OPTION = - NoCalleeFoundFallbackOptions.BYPASS; + NoCalleeFoundFallbackOptions.PRECOMPUTED; private static final Multimap didNotFindMethodLog = HashMultimap.create(); private final CallGraph precomputedCallGraph; private final WeightedBoomerang solver; private final Set queriedInvokeExprAndAllocationSitesFound = new LinkedHashSet<>(); - private final Set queriedInvokeExpr = new LinkedHashSet<>(); - - public BoomerangResolver(FrameworkScope frameworkScope) { - this.solver = new Boomerang(frameworkScope); - this.precomputedCallGraph = frameworkScope.getCallGraph(); - } + private Set queriedInvokeExpr = new LinkedHashSet<>(); public BoomerangResolver(WeightedBoomerang solver, CallGraph initialCallGraph) { - this(solver, true, initialCallGraph); - } - - public BoomerangResolver( - WeightedBoomerang solver, - boolean enableExceptions, - CallGraph initialCallGraph) { this.solver = solver; this.precomputedCallGraph = initialCallGraph; } @Override - public void computeFallback(ObservableDynamicICFG observableDynamicICFG) { + public boolean computeFallback( + BiConsumer onCallerCalleeFoundCallback, + Consumer onNoCalleeFoundCallback) { int refined = 0; int precomputed = 0; - for (Statement s : Lists.newArrayList(queriedInvokeExpr)) { + Set todo = queriedInvokeExpr; + queriedInvokeExpr = new LinkedHashSet<>(); + boolean changes = false; + for (Statement s : todo) { if (!queriedInvokeExprAndAllocationSitesFound.contains(s)) { logger.debug("Call graph ends at {}", s); precomputed++; + changes = true; if (FALLBACK_OPTION == NoCalleeFoundFallbackOptions.PRECOMPUTED) { + // strictly speaking, no alloc site was found - but we shouldn't process this stmt again + queriedInvokeExprAndAllocationSitesFound.add(s); for (CallGraph.Edge e : precomputedCallGraph.edgesOutOf(s)) { // TODO Refactor. Should not be required, if the backward analysis is sound (data-flow // of static fields) if (e.tgt().isDefined()) { - observableDynamicICFG.addCallIfNotInGraph(e.src(), e.tgt()); + onCallerCalleeFoundCallback.accept(e.src(), e.tgt()); } } } if (FALLBACK_OPTION == NoCalleeFoundFallbackOptions.BYPASS) { - observableDynamicICFG.notifyNoCalleeFound(s); + onNoCalleeFoundCallback.accept(s); } } else { refined++; } } logger.debug("Refined edges {}, fallback to precomputed {}", refined, precomputed); + return changes; + } + + @Override + public void resolveCallersForCalleeFallback( + Method callee, BiConsumer onCallerCalleeFoundCallback) { + Collection edges = precomputedCallGraph.edgesInto(callee); + for (CallGraph.Edge edge : edges) { + onCallerCalleeFoundCallback.accept(edge.src(), edge.tgt()); + } } @Override - public Method resolveSpecialInvoke(InvokeExpr ie) { + public void resolveSpecialInvoke( + Statement stmt, BiConsumer onCallerCalleeFoundCallback) { + InvokeExpr ie = stmt.getInvokeExpr(); Collection methodFromClassOrFromSuperclass = getMethodFromClassOrFromSuperclass( ie.getDeclaredMethod(), ie.getDeclaredMethod().getDeclaringClass()); if (methodFromClassOrFromSuperclass.size() > 1) { throw new RuntimeException( "Illegal state, a special call should exactly resolve to one target"); + } else if (!methodFromClassOrFromSuperclass.isEmpty()) { + onCallerCalleeFoundCallback.accept( + stmt, methodFromClassOrFromSuperclass.stream().findFirst().get()); } - return Iterables.getFirst(methodFromClassOrFromSuperclass, null); } @Override - public Method resolveStaticInvoke(InvokeExpr ie) { + public void resolveStaticInvoke( + Statement stmt, BiConsumer onCallerCalleeFoundCallback) { + InvokeExpr ie = stmt.getInvokeExpr(); Collection methodFromClassOrFromSuperclass = getMethodFromClassOrFromSuperclass( ie.getDeclaredMethod(), ie.getDeclaredMethod().getDeclaringClass()); if (methodFromClassOrFromSuperclass.size() > 1) { throw new RuntimeException( "Illegal state, a static call should exactly resolve to one target"); + } else if (!methodFromClassOrFromSuperclass.isEmpty()) { + onCallerCalleeFoundCallback.accept( + stmt, methodFromClassOrFromSuperclass.stream().findFirst().get()); } - return Iterables.getFirst(methodFromClassOrFromSuperclass, null); } @Override - public Collection resolveInstanceInvoke(Statement stmt) { - return queryForCallees(stmt); - } - - private Collection queryForCallees(Statement resolvingStmt) { + public void resolveInstanceInvoke( + Statement resolvingStmt, BiConsumer onCallerCalleeFoundCallback) { logger.debug("Queried for callees of '{}'.", resolvingStmt); // Construct BackwardQuery, so we know which types the object might have InvokeExpr invokeExpr = resolvingStmt.getInvokeExpr(); queriedInvokeExpr.add(resolvingStmt); Val value = invokeExpr.getBase(); - Collection res = new ArrayList<>(); - // Not using cfg here because we are iterating backward for (Statement pred : resolvingStmt.getMethod().getControlFlowGraph().getPredsOf(resolvingStmt)) { BackwardQuery query = BackwardQuery.make(new Edge(pred, resolvingStmt), value); - solver.solve(query, false); - res.addAll(forAnyAllocationSiteOfQuery(query, resolvingStmt, pred)); + solver.solve(query, false, false); + solver.registerSolverCreationListener( + new IterateSolvers(query, resolvingStmt, onCallerCalleeFoundCallback)); } - - return res; - } - - @SuppressWarnings("rawtypes") - private Collection forAnyAllocationSiteOfQuery( - BackwardQuery query, Statement resolvingStmt, Statement callSite) { - IterateSolvers callback = new IterateSolvers(query, callSite, resolvingStmt); - solver.registerSolverCreationListener(callback); - return callback.results; } + // XXX: interface default methods private Collection getMethodFromClassOrFromSuperclass( DeclaredMethod method, WrappedClass sootClass) { Set res = new LinkedHashSet<>(); @@ -182,8 +181,10 @@ private Collection getMethodFromClassOrFromSuperclass( res.add(candidate); } } - handlingForThreading(method, sootClass, res); - if (!res.isEmpty()) return res; + if (!res.isEmpty()) { + handlingForThreading(method, originalClass, res); + return res; + } if (sootClass.hasSuperclass()) { sootClass = sootClass.getSuperclass(); } else { @@ -202,29 +203,45 @@ private void logDidNotFindMethod(DeclaredMethod method, WrappedClass originalCla } private void handlingForThreading( - DeclaredMethod method, WrappedClass sootClass, Set res) { - // throw new RuntimeException("Threading not implemented"); - // if (Scene.v().getFastHierarchy().isSubclass(sootClass, - // Scene.v().getSootClass(THREAD_CLASS))) - // { - // if (method.getSignature().equals(THREAD_START_SIGNATURE)) { - // for (SootMethod candidate : sootClass.getMethods()) { - // if (candidate.getSubSignature().equals(THREAD_RUN_SUB_SIGNATURE)) { - // res.add(candidate); - // } - // } - // } - // } + DeclaredMethod method, WrappedClass wrappedClass, Set res) { + if (!THREAD_START_SIGNATURE.equals(method.getSignature())) { + return; + } + List lookupClasses = new ArrayList<>(); + lookupClasses.add(wrappedClass); + boolean inheritsFromThreadClass = THREAD_CLASS.equals(wrappedClass.getFullyQualifiedName()); + while (wrappedClass.hasSuperclass() && !inheritsFromThreadClass) { + wrappedClass = wrappedClass.getSuperclass(); + lookupClasses.add(wrappedClass); + inheritsFromThreadClass = THREAD_CLASS.equals(wrappedClass.getFullyQualifiedName()); + } + if (!inheritsFromThreadClass) { + return; + } + for (WrappedClass candidateClass : lookupClasses) { + Optional runMethod = + candidateClass.getMethods().stream() + .filter(m -> THREAD_RUN_SUB_SIGNATURE.equals(m.getSubSignature())) + .findFirst(); + if (runMethod.isPresent()) { + res.add(runMethod.get()); + break; + } + } } private final class IterateSolvers implements SolverCreationListener { private final BackwardQuery query; private final Statement invokeExpr; - private final Collection results = new ArrayList<>(); + private final BiConsumer onCallerCalleeFoundCallback; - private IterateSolvers(BackwardQuery query, Statement unit, Statement invokeExpr) { + private IterateSolvers( + BackwardQuery query, + Statement invokeExpr, + BiConsumer onCallerCalleeFoundCallback) { this.query = query; this.invokeExpr = invokeExpr; + this.onCallerCalleeFoundCallback = onCallerCalleeFoundCallback; } @Override @@ -232,40 +249,45 @@ public void onCreatedSolver(Query q, AbstractBoomerangSolver solver) { if (solver instanceof ForwardBoomerangSolver) { ForwardQuery forwardQuery = (ForwardQuery) q; ForwardBoomerangSolver forwardBoomerangSolver = (ForwardBoomerangSolver) solver; - for (INode> initialState : - forwardBoomerangSolver.getFieldAutomaton().getInitialStates()) { - forwardBoomerangSolver - .getFieldAutomaton() - .registerListener( - new ExtractAllocationSiteStateListener(initialState, query, (ForwardQuery) q) { + forwardBoomerangSolver + .getFieldAutomaton() + .registerListener( + initialState -> + forwardBoomerangSolver + .getFieldAutomaton() + .registerListener( + new ExtractAllocationSiteStateListener( + initialState, query, (ForwardQuery) q) { - @Override - protected void allocationSiteFound( - ForwardQuery allocationSite, BackwardQuery query) { - logger.debug("Found AllocationSite '{}'.", forwardQuery); - queriedInvokeExprAndAllocationSitesFound.add(invokeExpr); - Type type = forwardQuery.getType(); - if (type.isRefType()) { - for (Method calleeMethod : - getMethodFromClassOrFromSuperclass( - invokeExpr.getInvokeExpr().getDeclaredMethod(), - type.getWrappedClass())) { - results.add(calleeMethod); - } - } else if (type.isArrayType()) { - Type base = type.getArrayBaseType(); - if (base.isRefType()) { - for (Method calleeMethod : - getMethodFromClassOrFromSuperclass( - invokeExpr.getInvokeExpr().getDeclaredMethod(), - base.getWrappedClass())) { - results.add(calleeMethod); - } - } - } - } - }); - } + @Override + protected void allocationSiteFound( + ForwardQuery allocationSite, BackwardQuery query) { + logger.debug("Found AllocationSite '{}'.", forwardQuery); + queriedInvokeExprAndAllocationSitesFound.add(invokeExpr); + Type type = forwardQuery.getType(); + if (type.isNullType()) { + return; + } + if (type.isRefType()) { + for (Method calleeMethod : + getMethodFromClassOrFromSuperclass( + invokeExpr.getInvokeExpr().getDeclaredMethod(), + type.getWrappedClass())) { + onCallerCalleeFoundCallback.accept(invokeExpr, calleeMethod); + } + } else if (type.isArrayType()) { + Type base = type.getArrayBaseType(); + if (base.isRefType()) { + for (Method calleeMethod : + getMethodFromClassOrFromSuperclass( + invokeExpr.getInvokeExpr().getDeclaredMethod(), + base.getWrappedClass())) { + onCallerCalleeFoundCallback.accept(invokeExpr, calleeMethod); + } + } + } + } + })); } } diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/ICallerCalleeResolutionStrategy.java b/boomerangPDS/src/main/java/boomerang/callgraph/ICallerCalleeResolutionStrategy.java index 7f07dc9a..0d964df3 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/ICallerCalleeResolutionStrategy.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/ICallerCalleeResolutionStrategy.java @@ -16,10 +16,10 @@ import boomerang.WeightedBoomerang; import boomerang.scope.CallGraph; -import boomerang.scope.InvokeExpr; import boomerang.scope.Method; import boomerang.scope.Statement; -import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public interface ICallerCalleeResolutionStrategy { @@ -27,11 +27,19 @@ interface Factory { ICallerCalleeResolutionStrategy newInstance(WeightedBoomerang solver, CallGraph cg); } - void computeFallback(ObservableDynamicICFG observableDynamicICFG); + boolean computeFallback( + BiConsumer onCallerCalleeFoundCallback, + Consumer onNoCalleeFoundCallback); - Method resolveSpecialInvoke(InvokeExpr ie); + void resolveCallersForCalleeFallback( + Method callee, BiConsumer onCallerCalleeFoundCallback); - Collection resolveInstanceInvoke(Statement stmt); + void resolveSpecialInvoke( + Statement stmt, BiConsumer onCallerCalleeFoundCallback); - Method resolveStaticInvoke(InvokeExpr ie); + void resolveInstanceInvoke( + Statement stmt, BiConsumer onCallerCalleeFoundCallback); + + void resolveStaticInvoke( + Statement stmt, BiConsumer onCallerCalleeFoundCallback); } diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableDynamicICFG.java b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableDynamicICFG.java index a3e4dd7d..43aea213 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableDynamicICFG.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableDynamicICFG.java @@ -24,6 +24,10 @@ import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,7 +48,6 @@ public class ObservableDynamicICFG implements ObservableICFG private int numberOfEdgesTakenFromPrecomputedCallGraph = 0; - private final CallGraphOptions options = new CallGraphOptions(); private CallGraph demandDrivenCallGraph = new CallGraph(); private final Multimap> calleeListeners = @@ -52,8 +55,12 @@ public class ObservableDynamicICFG implements ObservableICFG private final Multimap> callerListeners = HashMultimap.create(); + private final Set> processedCallerListeners = new HashSet<>(); + private final ObservableControlFlowGraph cfg; private final ICallerCalleeResolutionStrategy resolutionStrategy; + private final BiConsumer onCallerCalleeFoundCallback = + (stmt, method) -> addEdge(new CallGraph.Edge(stmt, method)); public ObservableDynamicICFG( ObservableControlFlowGraph cfg, ICallerCalleeResolutionStrategy resolutionStrategy) { @@ -81,7 +88,7 @@ public void addCalleeListener(CalleeListener listener) { } } - for (Edge e : edges) { + for (Edge e : Lists.newArrayList(edges)) { if (e.tgt().isDefined()) { listener.onCalleeAdded(stmt, e.tgt()); } @@ -92,16 +99,14 @@ public void addCalleeListener(CalleeListener listener) { if ((ie.isInstanceInvokeExpr())) { // If it was invoked on an object we might find new instances if (ie.isSpecialInvokeExpr()) { - addCallIfNotInGraph(stmt, resolutionStrategy.resolveSpecialInvoke(ie)); + resolutionStrategy.resolveSpecialInvoke(stmt, onCallerCalleeFoundCallback); } else { // Query for callees of the unit and add edges to the graph - for (Method method : resolutionStrategy.resolveInstanceInvoke(stmt)) { - addCallIfNotInGraph(stmt, method); - } + resolutionStrategy.resolveInstanceInvoke(stmt, onCallerCalleeFoundCallback); } } else { // Call was not invoked on an object. Must be static - addCallIfNotInGraph(stmt, resolutionStrategy.resolveStaticInvoke(ie)); + resolutionStrategy.resolveStaticInvoke(stmt, onCallerCalleeFoundCallback); } } @@ -129,17 +134,22 @@ public void addCallerListener(CallerListener listener) { for (Edge e : Lists.newArrayList(edges)) { listener.onCallerAdded(e.src(), method); } + if (!edges.isEmpty()) { + processedCallerListeners.add(listener); + } } /** * Returns true if the call was added to the call graph, false if it was already present and the * call graph did not change */ - protected boolean addCallIfNotInGraph(Statement caller, Method callee) { - Edge edge = new Edge(caller, callee); + @Override + public void addEdge(Edge edge) { if (!demandDrivenCallGraph.addEdge(edge)) { - return false; + return; } + Statement caller = edge.src(); + Method callee = edge.tgt(); logger.debug("Added call from unit '{}' to method '{}'", caller, callee); // Notify all interested listeners, so .. // .. CalleeListeners interested in callees of the caller or the CallGraphExtractor that is @@ -154,7 +164,6 @@ protected boolean addCallIfNotInGraph(Statement caller, Method callee) { Lists.newArrayList(callerListeners.get(callee))) { listener.onCallerAdded(caller, callee); } - return true; } protected void notifyNoCalleeFound(Statement s) { @@ -203,11 +212,39 @@ public void resetCallGraph() { @Override public void computeFallback() { - resolutionStrategy.computeFallback(this); + boolean changes = false; + do { + /* + * Idea/"heuristic": run the caller listeners fallback before the callee + * listeners fallback (the hope is that the backward queries, which were + * issued by the resolutionStrategy, are resolved, when discovering new + * callers). + */ + do { + changes = runCallerListeners(); + } while (changes); + changes = resolutionStrategy.computeFallback( + onCallerCalleeFoundCallback, stmt -> notifyNoCalleeFound(stmt)); + } while (changes); } - @Override - public void addEdges(Edge e) { - demandDrivenCallGraph.addEdge(e); + private boolean runCallerListeners() { + int count = processedCallerListeners.size(); + Set> todo; + do { + todo = + callerListeners.values().stream() + .filter(l -> !processedCallerListeners.contains(l)) + .collect(Collectors.toSet()); + for (CallerListener listener : todo) { + if (processedCallerListeners.contains(listener)) { + continue; + } + processedCallerListeners.add(listener); + resolutionStrategy.resolveCallersForCalleeFallback( + listener.getObservedCallee(), onCallerCalleeFoundCallback); + } + } while (!todo.isEmpty()); + return count != processedCallerListeners.size(); } } diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableICFG.java b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableICFG.java index dbd07dcd..1675e308 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableICFG.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableICFG.java @@ -62,5 +62,5 @@ public interface ObservableICFG { void computeFallback(); - void addEdges(Edge e); + void addEdge(Edge e); } diff --git a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableStaticICFG.java b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableStaticICFG.java index a917c034..6a777e35 100644 --- a/boomerangPDS/src/main/java/boomerang/callgraph/ObservableStaticICFG.java +++ b/boomerangPDS/src/main/java/boomerang/callgraph/ObservableStaticICFG.java @@ -127,7 +127,7 @@ public void computeFallback() { } @Override - public void addEdges(Edge e) { + public void addEdge(Edge e) { throw new RuntimeException("Unnecessary"); } } diff --git a/boomerangPDS/src/main/java/boomerang/solver/AbstractBoomerangSolver.java b/boomerangPDS/src/main/java/boomerang/solver/AbstractBoomerangSolver.java index 0ab057dc..c2de60cb 100644 --- a/boomerangPDS/src/main/java/boomerang/solver/AbstractBoomerangSolver.java +++ b/boomerangPDS/src/main/java/boomerang/solver/AbstractBoomerangSolver.java @@ -159,9 +159,12 @@ public void onWeightAdded( : t.getLabel().getStart()))) { Statement exitStmt = t.getLabel().getTarget(); Method callee = exitStmt.getMethod(); - if (callAutomaton.getInitialStates().contains(t.getTarget())) { - addPotentialUnbalancedFlow(callee, t, w); - } + callAutomaton.registerListener( + initialState -> { + if (initialState.equals(t.getTarget())) { + addPotentialUnbalancedFlow(callee, t, w); + } + }); } } } @@ -318,10 +321,12 @@ public void allowUnbalanced(Method callee, Statement callSite) { } } - protected boolean isMatchingCallSiteCalleePair(Statement callSite, Method method) { - Set callSitesOfCall = new LinkedHashSet<>(); + protected void onMatchingCallSiteCalleePair( + Statement callSite, Method method, Runnable callback) { icfg.addCallerListener( new CallerListener<>() { + private boolean isCallbackExecuted = false; + @Override public Method getObservedCallee() { return method; @@ -329,10 +334,12 @@ public Method getObservedCallee() { @Override public void onCallerAdded(Statement statement, Method method) { - callSitesOfCall.add(statement); + if (!isCallbackExecuted && callSite.equals(statement)) { + isCallbackExecuted = true; + callback.run(); + } } }); - return callSitesOfCall.contains(callSite); } protected abstract void propagateUnbalancedToCallSite( diff --git a/boomerangPDS/src/main/java/boomerang/solver/BackwardBoomerangSolver.java b/boomerangPDS/src/main/java/boomerang/solver/BackwardBoomerangSolver.java index 334080be..f17dc932 100644 --- a/boomerangPDS/src/main/java/boomerang/solver/BackwardBoomerangSolver.java +++ b/boomerangPDS/src/main/java/boomerang/solver/BackwardBoomerangSolver.java @@ -300,40 +300,41 @@ protected void propagateUnbalancedToCallSite( if (!callSite.containsInvokeExpr()) { throw new RuntimeException("Invalid propagate Unbalanced return"); } - if (!isMatchingCallSiteCalleePair(callSite, transInCallee.getLabel().getMethod())) { - return; - } - cfg.addSuccsOfListener( - new SuccessorListener(callSite) { - @Override - public void getSuccessor(Statement succ) { - cfg.addPredsOfListener( - new PredecessorListener(callSite) { + onMatchingCallSiteCalleePair( + callSite, + transInCallee.getLabel().getMethod(), + () -> + cfg.addSuccsOfListener( + new SuccessorListener(callSite) { @Override - public void getPredecessor(Statement pred) { - Node curr = - new Node<>(new Edge(callSite, succ), query.var()); - - Transition> callTrans = - new Transition<>( - wrap(curr.fact()), - curr.stmt(), - generateCallState(wrap(curr.fact()), curr.stmt())); - callAutomaton.addTransition(callTrans); - callAutomaton.addUnbalancedState( - generateCallState(wrap(curr.fact()), curr.stmt()), target); - - State s = - new PushNode<>( - target.location(), - target.node().fact(), - new Edge(pred, callSite), - PDSSystem.CALLS); - propagate(curr, s); + public void getSuccessor(Statement succ) { + cfg.addPredsOfListener( + new PredecessorListener(callSite) { + @Override + public void getPredecessor(Statement pred) { + Node curr = + new Node<>(new Edge(callSite, succ), query.var()); + + Transition> callTrans = + new Transition<>( + wrap(curr.fact()), + curr.stmt(), + generateCallState(wrap(curr.fact()), curr.stmt())); + callAutomaton.addTransition(callTrans); + callAutomaton.addUnbalancedState( + generateCallState(wrap(curr.fact()), curr.stmt()), target); + + State s = + new PushNode<>( + target.location(), + target.node().fact(), + new Edge(pred, callSite), + PDSSystem.CALLS); + propagate(curr, s); + } + }); } - }); - } - }); + })); } private final class CallSiteCalleeListener implements CalleeListener { diff --git a/boomerangPDS/src/main/java/boomerang/solver/ForwardBoomerangSolver.java b/boomerangPDS/src/main/java/boomerang/solver/ForwardBoomerangSolver.java index d8d1c659..07f66b10 100644 --- a/boomerangPDS/src/main/java/boomerang/solver/ForwardBoomerangSolver.java +++ b/boomerangPDS/src/main/java/boomerang/solver/ForwardBoomerangSolver.java @@ -209,43 +209,44 @@ protected void propagateUnbalancedToCallSite( if (!callSite.containsInvokeExpr()) { throw new RuntimeException("Invalid propagate Unbalanced return"); } - if (!isMatchingCallSiteCalleePair(callSite, transInCallee.getLabel().getMethod())) { - return; - } - cfg.addSuccsOfListener( - new SuccessorListener(callSite) { - @Override - public void getSuccessor(Statement succ) { - cfg.addPredsOfListener( - new PredecessorListener(callSite) { + onMatchingCallSiteCalleePair( + callSite, + transInCallee.getLabel().getMethod(), + () -> + cfg.addSuccsOfListener( + new SuccessorListener(callSite) { @Override - public void getPredecessor(Statement pred) { - Node curr = - new Node<>(new Edge(pred, callSite), query.var()); - /* - * Transition>> fieldTrans = new - * Transition<>(new SingleNode<>(curr), emptyField(), new SingleNode<>(curr)); - * fieldAutomaton.addTransition(fieldTrans);* - */ - Transition> callTrans = - new Transition<>( - wrap(curr.fact()), - curr.stmt(), - generateCallState(wrap(curr.fact()), curr.stmt())); - callAutomaton.addTransition(callTrans); - callAutomaton.addUnbalancedState( - generateCallState(wrap(curr.fact()), curr.stmt()), target); - State s = - new PushNode<>( - target.location(), - target.node().fact(), - new Edge(callSite, succ), - PDSSystem.CALLS); - propagate(curr, s); + public void getSuccessor(Statement succ) { + cfg.addPredsOfListener( + new PredecessorListener(callSite) { + @Override + public void getPredecessor(Statement pred) { + Node curr = + new Node<>(new Edge(pred, callSite), query.var()); + /* + * Transition>> fieldTrans = new + * Transition<>(new SingleNode<>(curr), emptyField(), new SingleNode<>(curr)); + * fieldAutomaton.addTransition(fieldTrans);* + */ + Transition> callTrans = + new Transition<>( + wrap(curr.fact()), + curr.stmt(), + generateCallState(wrap(curr.fact()), curr.stmt())); + callAutomaton.addTransition(callTrans); + callAutomaton.addUnbalancedState( + generateCallState(wrap(curr.fact()), curr.stmt()), target); + State s = + new PushNode<>( + target.location(), + target.node().fact(), + new Edge(callSite, succ), + PDSSystem.CALLS); + propagate(curr, s); + } + }); } - }); - } - }); + })); } private final class CallSiteCalleeListener implements CalleeListener { diff --git a/boomerangPDS/src/test/java/boomerang/guided/CustomFlowFunctionTest.java b/boomerangPDS/src/test/java/boomerang/guided/CustomFlowFunctionTest.java index ec9bb7d8..32d93461 100644 --- a/boomerangPDS/src/test/java/boomerang/guided/CustomFlowFunctionTest.java +++ b/boomerangPDS/src/test/java/boomerang/guided/CustomFlowFunctionTest.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import test.TestingFramework; +import test.core.BoomerangTestingOptionsBuilder; import wpds.impl.NoWeight; public class CustomFlowFunctionTest { @@ -197,7 +198,7 @@ public static ForwardQuery selectFirstIntAssignment(Method method) { } private BoomerangOptions customOptions() { - return BoomerangOptions.builder() + return BoomerangTestingOptionsBuilder.create() .withAllocationSite(new IntAndStringAllocationSite()) .withFlowFunctionFactory(createFlowFunctionFactory()) .build(); diff --git a/boomerangPDS/src/test/java/boomerang/guided/DemandDrivenGuidedAnalysisTest.java b/boomerangPDS/src/test/java/boomerang/guided/DemandDrivenGuidedAnalysisTest.java index 916ceec9..d5ca6328 100644 --- a/boomerangPDS/src/test/java/boomerang/guided/DemandDrivenGuidedAnalysisTest.java +++ b/boomerangPDS/src/test/java/boomerang/guided/DemandDrivenGuidedAnalysisTest.java @@ -57,6 +57,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import test.TestingFramework; +import test.core.BoomerangTestingOptionsBuilder; import wpds.impl.NoWeight; public class DemandDrivenGuidedAnalysisTest { @@ -552,7 +553,7 @@ protected void runAnalysis( BackwardQuery query, Object... expectedValues) { BoomerangOptions options = - BoomerangOptions.builder() + BoomerangTestingOptionsBuilder.create() .withAllocationSite(allocationSite()) .enableAllowMultipleQueries(true) .build(); diff --git a/boomerangPDS/src/test/java/test/cases/bugfixes/issue5/Issue5Test.java b/boomerangPDS/src/test/java/test/cases/bugfixes/issue5/Issue5Test.java index e7762764..dc801741 100644 --- a/boomerangPDS/src/test/java/test/cases/bugfixes/issue5/Issue5Test.java +++ b/boomerangPDS/src/test/java/test/cases/bugfixes/issue5/Issue5Test.java @@ -16,7 +16,6 @@ import boomerang.Boomerang; import boomerang.ForwardQuery; -import boomerang.options.BoomerangOptions; import boomerang.options.IntAndStringAllocationSite; import boomerang.results.ForwardBoomerangResults; import boomerang.scope.AllocVal; @@ -37,6 +36,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import test.TestingFramework; +import test.core.BoomerangTestingOptionsBuilder; import wpds.impl.NoWeight; /** @@ -125,7 +125,10 @@ private static Collection getMethodsInvokedFromInstanceInStatement( ForwardQuery fwq = new ForwardQuery(new Edge(queryStatement, successorStmt.get()), var); Boomerang solver = new Boomerang( - scopeFactory, BoomerangOptions.withAllocationSite(new IntAndStringAllocationSite())); + scopeFactory, + BoomerangTestingOptionsBuilder.create() + .withAllocationSite(new IntAndStringAllocationSite()) + .build()); ForwardBoomerangResults results = solver.solve(fwq); return results.getInvokeStatementsOnInstance(); } diff --git a/boomerangPDS/src/test/java/test/core/BoomerangTestingFramework.java b/boomerangPDS/src/test/java/test/core/BoomerangTestingFramework.java index 22a928d4..5d1057b4 100644 --- a/boomerangPDS/src/test/java/test/core/BoomerangTestingFramework.java +++ b/boomerangPDS/src/test/java/test/core/BoomerangTestingFramework.java @@ -174,7 +174,7 @@ private void analyzeWithCallGraph(FrameworkScope frameworkScope, boolean ignoreA private void runWholeProgram(FrameworkScope frameworkScope) { final Set> results = new LinkedHashSet<>(); BoomerangOptions options = - BoomerangOptions.builder().withAnalysisTimeout(analysisTimeout).build(); + BoomerangTestingOptionsBuilder.create().withAnalysisTimeout(analysisTimeout).build(); WholeProgramBoomerang solver = new WholeProgramBoomerang<>(frameworkScope, options) { @@ -320,11 +320,12 @@ private Set> runQuery( } protected BoomerangOptions createBoomerangOptions() { + BoomerangOptions.OptionsBuilder builder = BoomerangTestingOptionsBuilder.create(); if (queryDetector.integerQueries) { - return BoomerangOptions.withAllocationSite(new IntAndStringAllocationSite()); + return builder.withAllocationSite(new IntAndStringAllocationSite()).build(); } - return BoomerangOptions.builder().withAnalysisTimeout(analysisTimeout).build(); + return builder.withAnalysisTimeout(analysisTimeout).build(); } private void compareQuery( diff --git a/boomerangPDS/src/test/java/test/core/BoomerangTestingOptionsBuilder.java b/boomerangPDS/src/test/java/test/core/BoomerangTestingOptionsBuilder.java new file mode 100644 index 00000000..5cba607d --- /dev/null +++ b/boomerangPDS/src/test/java/test/core/BoomerangTestingOptionsBuilder.java @@ -0,0 +1,57 @@ +/** + * ***************************************************************************** + * Copyright (c) 2018 Fraunhofer IEM, Paderborn, Germany + *

+ * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + *

+ * SPDX-License-Identifier: EPL-2.0 + *

+ * Contributors: + * Johannes Spaeth - initial API and implementation + * ***************************************************************************** + */ +package test.core; + +import boomerang.options.BoomerangOptions; + +/* + * Note: If you modify this class, you most likely want to modify its "copy" + * in the boomerangPDS module as well. In an ideal world this would be + * part of the TestingFramework class, which is part of the testCore + * maven module. However, this class needs access to the BoomerangOptions + * class (which in turn refers to other classes) that is part of the + * boomerangPDS maven module. Hence, this would lead to a cyclic + * dependency testCore <-> boomerangPDS. + * Note 2: If you are not in the mood to keep these two classes in sync, + * refactor it into a single class in a new boomerangOptions module. + * That would be the cleaner solution but it's not worth the effort + * atm, IMHO (note that the BoomerangOptions class or, more likely, an + * appropriate abstraction of that class has to be moved to that + * boomerangOptions maven module as well). + */ +public class BoomerangTestingOptionsBuilder { + private static final String DEFAULT_OTF_CALLGRAPH_OPTION = "false"; + + public static BoomerangOptions.OptionsBuilder create() { + BoomerangOptions.OptionsBuilder builder = BoomerangOptions.builder(); + String enableOnTheFlyCallGraph = + System.getProperty("enableOnTheFlyCallGraph", DEFAULT_OTF_CALLGRAPH_OPTION); + switch (enableOnTheFlyCallGraph) { + case "true": + builder.enableOnTheFlyCallGraph(true); + builder.enableAllowMultipleQueries(true); + break; + case "false": + builder.enableOnTheFlyCallGraph(false); + break; + default: + throw new IllegalArgumentException( + "Illegal value for the enableOnTheFlyCallGraph option: " + + enableOnTheFlyCallGraph + + " (valid values: true, false)"); + } + return builder; + } +} diff --git a/boomerangPDS/src/test/java/test/core/MultiQueryBoomerangTest.java b/boomerangPDS/src/test/java/test/core/MultiQueryBoomerangTest.java index 3ad7fab9..a2eab335 100644 --- a/boomerangPDS/src/test/java/test/core/MultiQueryBoomerangTest.java +++ b/boomerangPDS/src/test/java/test/core/MultiQueryBoomerangTest.java @@ -117,7 +117,7 @@ private void compareQuery(Query query, Collection results) { private void runDemandDrivenBackward(FrameworkScope frameworkScope) { BoomerangOptions options = - BoomerangOptions.builder() + BoomerangTestingOptionsBuilder.create() .withAnalysisTimeout(analysisTimeout) .enableAllowMultipleQueries(true) .build(); diff --git a/boomerangPDS/src/test/java/test/options/OptionsTestInterceptor.java b/boomerangPDS/src/test/java/test/options/OptionsTestInterceptor.java index 633b4c6e..ad531aa0 100644 --- a/boomerangPDS/src/test/java/test/options/OptionsTestInterceptor.java +++ b/boomerangPDS/src/test/java/test/options/OptionsTestInterceptor.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import test.core.BoomerangTestingOptionsBuilder; public class OptionsTestInterceptor implements InvocationInterceptor, AfterEachCallback { @@ -77,7 +78,7 @@ private BoomerangOptions createBoomerangOptions(TestOptions testOptions) { e); } - return BoomerangOptions.builder() + return BoomerangTestingOptionsBuilder.create() .withFlowFunctionFactory(flowFunctionFactory) .withArrayStrategy(testOptions.arrayStrategy()) .withStaticFieldStrategy(testOptions.staticFieldStrategy()) diff --git a/idealPDS/src/test/java/test/BoomerangTestingOptionsBuilder.java b/idealPDS/src/test/java/test/BoomerangTestingOptionsBuilder.java new file mode 100644 index 00000000..781671f1 --- /dev/null +++ b/idealPDS/src/test/java/test/BoomerangTestingOptionsBuilder.java @@ -0,0 +1,57 @@ +/** + * ***************************************************************************** + * Copyright (c) 2018 Fraunhofer IEM, Paderborn, Germany + *

+ * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + *

+ * SPDX-License-Identifier: EPL-2.0 + *

+ * Contributors: + * Johannes Spaeth - initial API and implementation + * ***************************************************************************** + */ +package test; + +import boomerang.options.BoomerangOptions; + +/* + * Note: If you modify this class, you most likely want to modify its "copy" + * in the boomerangPDS module as well. In an ideal world (pun not + * intended), this would be part of the TestingFramework class, which is + * part of the testCore maven module. However, this class needs access to + * the BoomerangOptions class (which in turn refers to other classes) + * that is part of the boomerangPDS maven module. Hence, this would lead + * to a cyclic dependency testCore <-> boomerangPDS. + * Note 2: If you are not in the mood to keep these two classes in sync, + * refactor it into a single class in a new boomerangOptions module. + * That would be the cleaner solution but it's not worth the effort + * atm, IMHO (note that the BoomerangOptions class or, more likely, an + * appropriate abstraction of that class has to be moved to that + * boomerangOptions maven module as well). + */ +public class BoomerangTestingOptionsBuilder { + private static final String DEFAULT_OTF_CALLGRAPH_OPTION = "false"; + + public static BoomerangOptions.OptionsBuilder create() { + BoomerangOptions.OptionsBuilder builder = BoomerangOptions.builder(); + String enableOnTheFlyCallGraph = + System.getProperty("enableOnTheFlyCallGraph", DEFAULT_OTF_CALLGRAPH_OPTION); + switch (enableOnTheFlyCallGraph) { + case "true": + builder.enableOnTheFlyCallGraph(true); + builder.enableAllowMultipleQueries(true); + break; + case "false": + builder.enableOnTheFlyCallGraph(false); + break; + default: + throw new IllegalArgumentException( + "Illegal value for the enableOnTheFlyCallGraph option: " + + enableOnTheFlyCallGraph + + " (valid values: true, false)"); + } + return builder; + } +} diff --git a/idealPDS/src/test/java/test/IDEALTestRunnerInterceptor.java b/idealPDS/src/test/java/test/IDEALTestRunnerInterceptor.java index ff3db30f..2ae5f3ec 100644 --- a/idealPDS/src/test/java/test/IDEALTestRunnerInterceptor.java +++ b/idealPDS/src/test/java/test/IDEALTestRunnerInterceptor.java @@ -135,7 +135,7 @@ public void afterEach(ExtensionContext context) { private BoomerangOptions createOptions(TestConfig config) { IFlowFunctionFactory factory = getFlowFunctionFactory(config.flowFunctions()); - return BoomerangOptions.builder() + return BoomerangTestingOptionsBuilder.create() .withFlowFunctionFactory(factory) .withStaticFieldStrategy(Strategies.StaticFieldStrategy.FLOW_SENSITIVE) .withAnalysisTimeout(-1)