/*
* CPAAlgorithm.java - This file is part of the Jakstab project.
* Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
*/
package org.jakstab.analysis;
import java.util.*;
import org.jakstab.AnalysisManager;
import org.jakstab.Options;
import org.jakstab.Program;
import org.jakstab.Algorithm;
import org.jakstab.analysis.composite.CompositeProgramAnalysis;
import org.jakstab.analysis.composite.CompositeState;
import org.jakstab.analysis.location.BackwardLocationAnalysis;
import org.jakstab.analysis.location.LocationAnalysis;
import org.jakstab.cfa.*;
import org.jakstab.util.*;
/**
* The main CPA worklist algorithm.
*
* @author Johannes Kinder
*/
public class CPAAlgorithm implements Algorithm {
private static final Logger logger = Logger.getLogger(CPAAlgorithm.class);
private final StateTransformerFactory transformerFactory;
private final ConfigurableProgramAnalysis cpa;
private final ReachedSet reached;
private final AbstractReachabilityTree art;
private final Worklist<AbstractState> worklist;
private final boolean failFast;
private long statesVisited;
private boolean completed = false;
private volatile boolean stop = false;
/**
* Instantiates a new CPA algorithm with a forward location analysis, a default
* forward transformer factory and worklist suitable for an analysis of a complete
* and already reconstructed control flow automaton.
*
* @param cpas The list of analyses to be performed
*/
public static CPAAlgorithm createForwardAlgorithm(ControlFlowGraph cfg, ConfigurableProgramAnalysis... cpas) {
ConfigurableProgramAnalysis cpa = new CompositeProgramAnalysis(new LocationAnalysis(), cpas);
return new CPAAlgorithm(cpa, new CFATransformerFactory(cfg), new FastSet<AbstractState>());
}
/**
* Instantiates a new CPA algorithm with a backward location analysis, a default
* backward transformer factory and worklist suitable for an analysis of a complete
* and already reconstructed control flow automaton.
*
* @param cpas The list of backward analyses to be performed
*/
public static CPAAlgorithm createBackwardAlgorithm(ControlFlowGraph cfg, ConfigurableProgramAnalysis... cpas) {
ConfigurableProgramAnalysis cpa = new CompositeProgramAnalysis(new BackwardLocationAnalysis(), cpas);
return new CPAAlgorithm(cpa, new ReverseCFATransformerFactory(cfg), new FastSet<AbstractState>());
}
public CPAAlgorithm(ConfigurableProgramAnalysis cpa,
StateTransformerFactory transformerFactory, Worklist<AbstractState> worklist) {
this(cpa, transformerFactory, worklist, false);
}
public CPAAlgorithm(ConfigurableProgramAnalysis cpa,
StateTransformerFactory transformerFactory, Worklist<AbstractState> worklist, boolean failFast) {
super();
this.cpa = cpa;
this.transformerFactory = transformerFactory;
this.worklist = worklist;
this.failFast = failFast;
if (Options.errorTrace.getValue() || Options.asmTrace.getValue() ||
AnalysisManager.getInstance().getAnalysis(
org.jakstab.analysis.explicit.VpcTrackingAnalysis.class) != null)
art = new AbstractReachabilityTree();
else
art = null;
reached = new ReachedSet();
}
/**
* After a run of the algorithm, returns the set of reached states.
*
* @return the set of reached (and kept) states.
*/
public ReachedSet getReachedStates() {
return reached;
}
public AbstractReachabilityTree getART() {
return art;
}
public long getNumberOfStatesVisited() {
return statesVisited;
}
/**
* Returns whether the algorithm terminated normally.
*/
public boolean isCompleted() {
return completed;
}
/**
* Returns whether the algorithm had to make unsound assumptions. Always
* true for analyses on complete CFAs.
*
* @return true if the analysis required unsound assumptions.
*/
public boolean isSound() {
if (transformerFactory instanceof ResolvingTransformerFactory) {
return ((ResolvingTransformerFactory)transformerFactory).isSound();
} else {
return true;
}
}
@Override
public void run() {
logger.debug("Starting CPA algorithm.");
Runtime runtime = Runtime.getRuntime();
Program program = Program.getProgram();
AbstractState start = cpa.initStartState(transformerFactory.getInitialLocation());
worklist.add(start);
reached.add(start);
if (art != null) art.setRoot(start);
// Set up precisions
Precision precision = cpa.initPrecision(transformerFactory.getInitialLocation(), null);
Map<Location, Precision> precisionMap = new HashMap<Location, Precision>();
precisionMap.put(start.getLocation(), precision);
int steps = 0;
statesVisited = 0;
final int stepThreshold = 1000;
long startTime = System.currentTimeMillis();
long lastSteps = 0;
long lastTime = 0;
while (!worklist.isEmpty() && !stop && (!failFast || isSound())) {
statesVisited++;
if (++steps == stepThreshold) {
// Helps limit memory usage
long now = System.currentTimeMillis();
System.gc();
long gcTime = System.currentTimeMillis() - now;
logger.debug("Time for GC: " + gcTime + "ms");
now = System.currentTimeMillis();
long duration = Math.max(1, now - lastTime);
long speed = (1000L*(statesVisited - lastSteps) / duration);
//speed = Math.min(speed, 1000);
logger.warn("*** Reached " + reached.size() + " states, processed " +
statesVisited + " states after " + (now - startTime) + "ms, at " +
speed + " states/second" +
(transformerFactory instanceof ResolvingTransformerFactory ?
", " + program.getInstructionCount() + " instructions."
: "."));
logger.info(String.format(" Allocated heap memory: %.2f MByte", (runtime.totalMemory() - runtime.freeMemory())/(1024.0*1024.0)));
steps = 0;
//StatsPlotter.plot((now - startTime) + "\t" + statesVisited +"\t" + program.getInstructionCount() + "\t" + gcTime + "\t" + speed);
lastSteps = statesVisited;
lastTime = now;
if (Options.timeout.getValue() > 0 && (System.currentTimeMillis() - startTime > Options.timeout.getValue() * 1000)) {
logger.error("Timeout after " + Options.timeout.getValue() + "s!");
stop = true;
}
}
// We need the state before precision refinement for building the ART
AbstractState unadjustedState = worklist.pick();
// Prefix everything by current location for easier debugging
//Logger.setGlobalPrefix(unadjustedState.getLocation().toString());
precision = precisionMap.get(unadjustedState.getLocation());
Pair<AbstractState, Precision> pair = cpa.prec(unadjustedState, precision, reached);
// Warning: The refined a is not stored in "reached", only used for successor calculation
AbstractState a = pair.getLeft();
precision = pair.getRight();
precisionMap.put(a.getLocation(), precision);
//logger.debug("Picked from worklist: " + a.getIdentifier());
// getTransformers() might throw exceptions
try {
// For each outgoing edge
for (CFAEdge cfaEdge : transformerFactory.getTransformers(a)) {
Precision targetPrecision = precisionMap.get(cfaEdge.getTarget());
if (targetPrecision == null) {
targetPrecision = cpa.initPrecision(cfaEdge.getTarget(), cfaEdge.getTransformer());
precisionMap.put(cfaEdge.getTarget(), targetPrecision);
}
// Calculate the set of abstract successors
// post() might throw exceptions
Set<AbstractState> successors;
try {
successors = cpa.post(a, cfaEdge, targetPrecision);
} catch (StateException e) {
if (e.getState() == null) {
e.setState(a);
}
if (art != null && !unadjustedState.equals(e.getState()))
art.addChild(unadjustedState, cfaEdge, e.getState());
throw e;
}
if (successors.isEmpty()) {
logger.debug("No successors along edge " + cfaEdge);
continue;
}
//logger.debug("via edge " + cfaEdge.toString() + " " + successors.size() + " successors.");
// Process every successor
for (AbstractState succ : successors) {
//logger.debug("Processing new post state: " + succ.getIdentifier());
// Try to merge the new state with an existing one
Set<AbstractState> statesToRemove = new FastSet<AbstractState>();
Set<AbstractState> statesToAdd = new FastSet<AbstractState>();
for (AbstractState r : reached.where(0, ((CompositeState)succ).getComponent(0))) {
AbstractState merged = cpa.merge(succ, r, targetPrecision);
if (!merged.equals(r)) {
//logger.debug("Merge of new successor:\n" + succ + "\n and reached state:\n" + r + "\n produced new state \n" + merged);
statesToRemove.add(r);
statesToAdd.add(merged);
}
}
// replace the old state in worklist and reached with the merged version
for (AbstractState r : statesToRemove) {
reached.remove(r);
worklist.remove(r);
//art.remove(r);
}
for (AbstractState r : statesToAdd) {
// Only add r to the worklist if it hasn't been reached yet
if (reached.add(r)) {
worklist.add(r);
if (art != null) art.addChild(unadjustedState, cfaEdge, r);
}
}
// if not stopped add to worklist
if (!cpa.stop(succ, reached, targetPrecision)) {
/*if (!statesToAdd.isEmpty()) {
logger.verbose("Merged successor with " + statesToAdd.size() + " states, but still adding it to reached and worklist:");
logger.warn(succ);
}*/
worklist.add(succ);
reached.add(succ);
if (art != null) art.addChild(unadjustedState, cfaEdge, succ);
}
}
// end for each outgoing edge
}
} catch (StateException e) {
// Fill in state for disassembly and unknownpointer exceptions
if (e.getState() == null) {
e.setState(a);
}
throw e;
}
}
long endTime = System.currentTimeMillis();
if (endTime - startTime > 0) {
logger.info("Processed " + statesVisited + " states at " + (1000L*statesVisited / (endTime - startTime)) + " states/second");
logger.info(String.format("Allocated heap memory: %.2f MByte", (runtime.totalMemory() - runtime.freeMemory())/(1024.0*1024.0)));
}
completed = worklist.isEmpty();
}
public void stop() {
logger.fatal(Characters.starredBox("Interrupt! Stopping CPA Algorithm!"));
stop = true;
}
}