boomerang.solver.AbstractBoomerangSolver Maven / Gradle / Ivy
/**
* ***************************************************************************** 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 boomerang.solver;
import boomerang.BoomerangOptions;
import boomerang.Query;
import boomerang.callgraph.BackwardsObservableICFG;
import boomerang.callgraph.CallerListener;
import boomerang.callgraph.ObservableICFG;
import boomerang.controlflowgraph.ObservableControlFlowGraph;
import boomerang.scene.AllocVal;
import boomerang.scene.ControlFlowGraph;
import boomerang.scene.ControlFlowGraph.Edge;
import boomerang.scene.DataFlowScope;
import boomerang.scene.Field;
import boomerang.scene.Method;
import boomerang.scene.Statement;
import boomerang.scene.Type;
import boomerang.scene.Val;
import boomerang.util.RegExAccessPath;
import com.google.common.base.Stopwatch;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.LoggerFactory;
import pathexpression.IRegEx;
import sync.pds.solver.EmptyStackWitnessListener;
import sync.pds.solver.SyncPDSSolver;
import sync.pds.solver.WitnessListener;
import sync.pds.solver.nodes.GeneratedState;
import sync.pds.solver.nodes.INode;
import sync.pds.solver.nodes.Node;
import sync.pds.solver.nodes.SingleNode;
import wpds.impl.NestedWeightedPAutomatons;
import wpds.impl.NormalRule;
import wpds.impl.Rule;
import wpds.impl.Transition;
import wpds.impl.Weight;
import wpds.impl.WeightedPAutomaton;
import wpds.impl.WeightedPushdownSystem;
import wpds.interfaces.State;
import wpds.interfaces.WPAUpdateListener;
public abstract class AbstractBoomerangSolver
extends SyncPDSSolver {
private static final org.slf4j.Logger LOGGER =
LoggerFactory.getLogger(AbstractBoomerangSolver.class);
protected final ObservableICFG icfg;
protected final ObservableControlFlowGraph cfg;
protected boolean INTERPROCEDURAL = true;
protected final Map<
Entry>, Field>,
INode>>
generatedFieldState;
private Multimap>>>
perMethodFieldTransitions = HashMultimap.create();
private Multimap>
perMethodFieldTransitionsListener = HashMultimap.create();
protected Multimap<
ControlFlowGraph.Edge, Transition>>>
perStatementFieldTransitions = HashMultimap.create();
private Multimap>
perStatementFieldTransitionsListener = HashMultimap.create();
private HashBasedTable>, W>
perStatementCallTransitions = HashBasedTable.create();
private Multimap>
perStatementCallTransitionsListener = HashMultimap.create();
private Multimap> unbalancedDataFlows = HashMultimap.create();
private Multimap unbalancedDataFlowListeners =
HashMultimap.create();
protected final DataFlowScope dataFlowScope;
protected final BoomerangOptions options;
protected final Type type;
public AbstractBoomerangSolver(
ObservableICFG icfg,
ObservableControlFlowGraph cfg,
Map>, Field>, INode>> genField,
BoomerangOptions options,
NestedWeightedPAutomatons, W> callSummaries,
NestedWeightedPAutomatons>, W> fieldSummaries,
DataFlowScope scope,
Type propagationType) {
super(
icfg instanceof BackwardsObservableICFG ? false : options.callSummaries(),
callSummaries,
options.fieldSummaries(),
fieldSummaries,
options.maxCallDepth(),
options.maxFieldDepth(),
options.maxUnbalancedCallDepth());
this.options = options;
this.icfg = icfg;
this.cfg = cfg;
this.dataFlowScope = scope;
this.type = propagationType;
this.fieldAutomaton.registerListener(
(t, w, aut) -> {
addTransitionToMethod(t.getStart().fact().stmt().getStart().getMethod(), t);
addTransitionToMethod(t.getTarget().fact().stmt().getStart().getMethod(), t);
addTransitionToStatement(t.getStart().fact().stmt(), t);
});
this.callAutomaton.registerListener(
(t, w, aut) -> {
addCallTransitionToStatement(t.getLabel(), t, w);
});
this.callAutomaton.registerListener(new UnbalancedListener());
this.generatedFieldState = genField;
}
public boolean reachesNodeWithEmptyField(Node node) {
for (Transition>> t : getFieldAutomaton().getTransitions()) {
if (t.getStart() instanceof GeneratedState) {
continue;
}
if (t.getStart().fact().equals(node) && t.getLabel().equals(Field.empty())) {
return true;
}
}
return false;
}
private class UnbalancedListener
implements WPAUpdateListener, W> {
@Override
public void onWeightAdded(
Transition> t, W w, WeightedPAutomaton, W> aut) {
if (t.getLabel().equals(new Edge(Statement.epsilon(), Statement.epsilon()))) return;
if (icfg.isExitStmt(
(AbstractBoomerangSolver.this instanceof ForwardBoomerangSolver
? t.getLabel().getTarget()
: t.getLabel().getStart()))) {
Statement exitStmt = t.getLabel().getTarget();
Method callee = exitStmt.getMethod();
if (callAutomaton.getInitialStates().contains(t.getTarget())) {
addPotentialUnbalancedFlow(callee, t, w);
}
}
}
}
public INode> createQueryNodeField(Query query) {
return new SingleNode(
/* TODO Replace by new designated type */ new Node<>(
query.cfgEdge(), query.asNode().fact().asUnbalanced(query.cfgEdge())));
}
public void synchedEmptyStackReachable(
final Node sourceNode, final EmptyStackWitnessListener listener) {
synchedReachable(
sourceNode,
new WitnessListener() {
Multimap> potentialFieldCandidate = HashMultimap.create();
Set potentialCallCandidate = Sets.newHashSet();
@Override
public void fieldWitness(Transition>> t) {
if (t.getTarget() instanceof GeneratedState) return;
if (!t.getLabel().equals(emptyField())) return;
Node targetFact =
new Node<>(
t.getTarget().fact().stmt(), t.getTarget().fact().fact().asUnbalanced(null));
if (!potentialFieldCandidate.put(targetFact.fact(), targetFact)) return;
if (potentialCallCandidate.contains(targetFact.fact())) {
listener.witnessFound(targetFact);
}
}
@Override
public void callWitness(Transition> t) {
Val targetFact = t.getTarget().fact();
if (targetFact instanceof AllocVal) {
targetFact = ((AllocVal) targetFact).getDelegate();
if (!potentialCallCandidate.add(targetFact)) return;
if (potentialFieldCandidate.containsKey(targetFact)) {
for (Node w : potentialFieldCandidate.get(targetFact)) {
listener.witnessFound(w);
}
}
}
}
});
}
public void synchedReachable(
final Node sourceNode,
final WitnessListener listener) {
registerListener(
reachableNode -> {
if (!reachableNode.equals(sourceNode)) return;
fieldAutomaton.registerListener(
(t, w, aut) -> {
if (t.getStart() instanceof GeneratedState) return;
if (!t.getStart().fact().equals(sourceNode)) return;
listener.fieldWitness(t);
});
callAutomaton.registerListener(
(t, w, aut) -> {
if (t.getStart() instanceof GeneratedState) return;
if (!t.getStart().fact().equals(sourceNode.fact())) return;
if (!t.getLabel().equals(sourceNode.stmt())) return;
if (callAutomaton.isUnbalancedState(t.getTarget())) {
listener.callWitness(t);
}
});
});
}
public Table asStatementValWeightTable() {
final Table results = HashBasedTable.create();
Stopwatch sw = Stopwatch.createStarted();
WeightedPAutomaton, W> callAut = getCallAutomaton();
for (Entry>, W> e :
callAut.getTransitionsToFinalWeights().entrySet()) {
Transition> t = e.getKey();
W w = e.getValue();
if (t.getLabel().equals(new Edge(Statement.epsilon(), Statement.epsilon()))) continue;
if (t.getStart().fact().isLocal()
&& !t.getLabel().getMethod().equals(t.getStart().fact().m())) continue;
results.put(t.getLabel(), t.getStart().fact(), w);
}
return results;
}
protected void addPotentialUnbalancedFlow(
Method callee, Transition> trans, W weight) {
if (unbalancedDataFlows.put(callee, new UnbalancedDataFlow<>(callee, trans))) {
Collection existingListeners =
Lists.newArrayList(unbalancedDataFlowListeners.get(callee));
for (UnbalancedDataFlowListener l : existingListeners) {
propagateUnbalancedToCallSite(l.getCallSiteEdge(), trans);
}
}
if (forceUnbalanced(trans.getTarget(), callAutomaton.getUnbalancedStartOf(trans.getTarget()))) {
icfg.addCallerListener(
new CallerListener() {
@Override
public Method getObservedCallee() {
return callee;
}
@Override
public void onCallerAdded(Statement n, Method m) {
propagateUnbalancedToCallSite(n, trans);
}
});
}
}
protected boolean forceUnbalanced(INode iNode, Collection> collection) {
return false;
}
public void allowUnbalanced(Method callee, Statement callSite) {
if (dataFlowScope.isExcluded(callee)) {
return;
}
UnbalancedDataFlowListener l = new UnbalancedDataFlowListener(callee, callSite);
if (unbalancedDataFlowListeners.put(callee, l)) {
LOGGER.trace("Allowing unbalanced propagation from {} to {} of {}", callee, callSite, this);
for (UnbalancedDataFlow e : Lists.newArrayList(unbalancedDataFlows.get(callee))) {
propagateUnbalancedToCallSite(callSite, e.getReturningTransition());
}
}
}
protected boolean isMatchingCallSiteCalleePair(Statement callSite, Method method) {
Set callsitesOfCall = Sets.newHashSet();
icfg.addCallerListener(
new CallerListener() {
@Override
public Method getObservedCallee() {
return method;
}
@Override
public void onCallerAdded(Statement statement, Method method) {
callsitesOfCall.add(statement);
}
});
return callsitesOfCall.contains(callSite);
}
protected abstract void propagateUnbalancedToCallSite(
Statement callSiteEdge, Transition> transInCallee);
@Override
protected boolean preventCallTransitionAdd(
Transition> t, W weight) {
return false;
}
@Override
public void addCallRule(final Rule, W> rule) {
if (rule instanceof NormalRule) {
if (rule.getL1().equals(rule.getL2()) && rule.getS1().equals(rule.getS2())) return;
}
super.addCallRule(rule);
}
@Override
public void addFieldRule(final Rule>, W> rule) {
if (rule instanceof NormalRule) {
if (rule.getL1().equals(rule.getL2()) && rule.getS1().equals(rule.getS2())) return;
}
super.addFieldRule(rule);
}
private void addTransitionToMethod(Method method, Transition>> t) {
if (perMethodFieldTransitions.put(method, t)) {
for (MethodBasedFieldTransitionListener l :
Lists.newArrayList(perMethodFieldTransitionsListener.get(method))) {
l.onAddedTransition(t);
}
}
}
public void registerFieldTransitionListener(MethodBasedFieldTransitionListener l) {
if (perMethodFieldTransitionsListener.put(l.getMethod(), l)) {
for (Transition>> t :
Lists.newArrayList(perMethodFieldTransitions.get(l.getMethod()))) {
l.onAddedTransition(t);
}
}
}
private void addTransitionToStatement(
ControlFlowGraph.Edge s, Transition>> t) {
if (perStatementFieldTransitions.put(s, t)) {
for (ControlFlowEdgeBasedFieldTransitionListener l :
Lists.newArrayList(perStatementFieldTransitionsListener.get(s))) {
l.onAddedTransition(t);
}
}
}
public void registerStatementFieldTransitionListener(
ControlFlowEdgeBasedFieldTransitionListener l) {
if (perStatementFieldTransitionsListener.put(l.getCfgEdge(), l)) {
for (Transition>> t :
Lists.newArrayList(perStatementFieldTransitions.get(l.getCfgEdge()))) {
l.onAddedTransition(t);
}
}
}
private void addCallTransitionToStatement(Edge s, Transition> t, W w) {
W put = perStatementCallTransitions.get(s, t);
if (put != null) {
W combineWith = (W) put.combineWith(w);
if (!combineWith.equals(put)) {
perStatementCallTransitions.put(s, t, combineWith);
for (ControlFlowEdgeBasedCallTransitionListener l :
Lists.newArrayList(perStatementCallTransitionsListener.get(s))) {
l.onAddedTransition(t, w);
}
}
} else {
perStatementCallTransitions.put(s, t, w);
for (ControlFlowEdgeBasedCallTransitionListener l :
Lists.newArrayList(perStatementCallTransitionsListener.get(s))) {
l.onAddedTransition(t, w);
}
}
}
public void registerStatementCallTransitionListener(
ControlFlowEdgeBasedCallTransitionListener l) {
if (perStatementCallTransitionsListener.put(l.getControlFlowEdge(), l)) {
Map>, W> row =
perStatementCallTransitions.row(l.getControlFlowEdge());
for (Entry>, W> t :
Lists.newArrayList(row.entrySet())) {
l.onAddedTransition(t.getKey(), t.getValue());
}
}
}
public INode> generateFieldState(
final INode> d, final Field loc) {
Entry>, Field> e = new AbstractMap.SimpleEntry<>(d, loc);
if (!generatedFieldState.containsKey(e)) {
generatedFieldState.put(e, new GeneratedState<>(d, loc));
}
return generatedFieldState.get(e);
}
private boolean isBackward() {
return this instanceof BackwardBoomerangSolver;
}
protected abstract Collection extends State> computeReturnFlow(
Method method, Statement curr, Val value);
protected void returnFlow(Method method, Node currNode) {
Val value = currNode.fact();
Collection extends State> outFlow =
computeReturnFlow(method, currNode.stmt().getTarget(), value);
for (State s : outFlow) {
propagate(currNode, s);
}
}
protected abstract Collection computeNormalFlow(Method method, Edge currEdge, Val value);
@Override
public Field epsilonField() {
return Field.epsilon();
}
@Override
public Field emptyField() {
return Field.empty();
}
@Override
public ControlFlowGraph.Edge epsilonStmt() {
return new Edge(Statement.epsilon(), Statement.epsilon());
}
@Override
public Field fieldWildCard() {
return Field.wildcard();
}
@Override
public Field exclusionFieldWildCard(Field exclusion) {
return Field.exclusionWildcard(exclusion);
}
public WeightedPAutomaton>, W> getFieldAutomaton() {
return fieldAutomaton;
}
public WeightedPAutomaton, W> getCallAutomaton() {
return callAutomaton;
}
public WeightedPushdownSystem, W> getCallPDS() {
return callingPDS;
}
public WeightedPushdownSystem>, W> getFieldPDS() {
return fieldPDS;
}
public int getNumberOfRules() {
return callingPDS.getAllRules().size() + fieldPDS.getAllRules().size();
}
@Override
protected boolean preventFieldTransitionAdd(
Transition>> t, W weight) {
if (t.getStart().equals(t.getTarget()) && t.getLabel().equals(Field.empty())) {
LOGGER.warn("Prevented illegal edge addition of {}", t);
return true;
}
if (!t.getLabel().equals(Field.empty()) || !options.typeCheck()) {
return false;
}
if (t.getTarget() instanceof GeneratedState || t.getStart() instanceof GeneratedState) {
return false;
}
Val target = t.getTarget().fact().fact();
Val source = t.getStart().fact().fact();
if (source.isStatic()) {
return false;
}
Type sourceVal = source.getType();
Type targetVal = (isBackward() ? target.getType() : type);
if (sourceVal == null) {
return true;
}
if (sourceVal.equals(targetVal)) {
return false;
}
if (!(targetVal.isRefType()) || !(sourceVal.isRefType())) {
if (options.killNullAtCast() && targetVal.isNullType() && isCastNode(t.getStart().fact())) {
// A null pointer cannot be cast to any object
return true;
}
return false; // !allocVal.value().getType().equals(varVal.value().getType());
}
return sourceVal.doesCastFail(targetVal, target);
}
private boolean isCastNode(Node node) {
boolean isCast = node.stmt().getStart().isCast();
if (isCast) {
Val rightOp = node.stmt().getStart().getRightOp();
if (rightOp.isCast()) {
if (rightOp.getCastOp().equals(node.fact())) {
return true;
}
}
}
return false;
}
public Map getResultsAt(final Statement stmt) {
final Map results = Maps.newHashMap();
fieldAutomaton.registerListener(
(t, w, aut) -> {
if (t.getStart() instanceof GeneratedState) {
return;
}
if (t.getStart().fact().stmt().equals(stmt)) {
for (INode> initState :
fieldAutomaton.getInitialStates()) {
IRegEx regEx = fieldAutomaton.toRegEx(t.getStart(), initState);
results.put(new RegExAccessPath(t.getStart().fact().fact(), regEx), w);
}
}
});
return results;
}
public Table getResults(Method m) {
final Table results = HashBasedTable.create();
LOGGER.debug("Start extracting results from {}", this);
fieldAutomaton.registerListener(
(t, w, aut) -> {
if (t.getStart() instanceof GeneratedState) {
return;
}
if (t.getStart().fact().stmt().getStart().getMethod().equals(m)) {
for (INode> initState :
fieldAutomaton.getInitialStates()) {
IRegEx regEx = fieldAutomaton.toRegEx(t.getStart(), initState);
AbstractBoomerangSolver.this.callAutomaton.registerListener(
(callT, w1, aut1) -> {
if (callT.getStart().fact().equals(t.getStart().fact().fact())
&& callT.getLabel().equals(t.getStart().fact().stmt())) {
results.put(
t.getStart().fact().stmt(),
new RegExAccessPath(t.getStart().fact().fact(), regEx),
w1);
}
});
}
}
});
LOGGER.debug("End extracted results from {}", this);
return results;
}
public void debugFieldAutomaton(final Statement stmt) {
fieldAutomaton.registerListener(
(t, w, aut) -> {
if (t.getStart() instanceof GeneratedState) {
return;
}
if (t.getStart().fact().stmt().equals(stmt)) {
for (INode> initState :
fieldAutomaton.getInitialStates()) {
IRegEx regEx = fieldAutomaton.toRegEx(t.getStart(), initState);
LOGGER.debug(t.getStart().fact().fact() + " " + regEx);
}
}
});
}
public void unregisterAllListeners() {
this.callAutomaton.unregisterAllListeners();
this.fieldAutomaton.unregisterAllListeners();
this.perMethodFieldTransitionsListener.clear();
this.perStatementCallTransitionsListener.clear();
this.perStatementFieldTransitionsListener.clear();
this.unbalancedDataFlowListeners.clear();
this.unbalancedDataFlows.clear();
this.callingPDS.unregisterAllListeners();
this.fieldPDS.unregisterAllListeners();
}
private static class UnbalancedDataFlow {
private final Method callee;
private Transition> trans;
public UnbalancedDataFlow(Method callee, Transition> trans) {
this.callee = callee;
this.trans = trans;
}
public Transition> getReturningTransition() {
return trans;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((callee == null) ? 0 : callee.hashCode());
result = prime * result + ((trans == null) ? 0 : trans.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UnbalancedDataFlow other = (UnbalancedDataFlow) obj;
if (callee == null) {
if (other.callee != null) return false;
} else if (!callee.equals(other.callee)) return false;
if (trans == null) {
if (other.trans != null) return false;
} else if (!trans.equals(other.trans)) return false;
return true;
}
}
private static class UnbalancedDataFlowListener {
private Method callee;
private Statement callSite;
public UnbalancedDataFlowListener(Method callee, Statement callSite) {
this.callee = callee;
this.callSite = callSite;
}
public Statement getCallSiteEdge() {
return callSite;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((callee == null) ? 0 : callee.hashCode());
result = prime * result + ((callSite == null) ? 0 : callSite.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
UnbalancedDataFlowListener other = (UnbalancedDataFlowListener) obj;
if (callee == null) {
if (other.callee != null) return false;
} else if (!callee.equals(other.callee)) return false;
if (callSite == null) {
if (other.callSite != null) return false;
} else if (!callSite.equals(other.callSite)) return false;
return true;
}
}
}