il.ac.bgu.cs.bp.bpjs.analysis.DfsBProgramVerifier Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright 2017 michael.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package il.ac.bgu.cs.bp.bpjs.analysis;
import il.ac.bgu.cs.bp.bpjs.analysis.violations.Violation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import il.ac.bgu.cs.bp.bpjs.model.BProgram;
import il.ac.bgu.cs.bp.bpjs.model.BEvent;
import il.ac.bgu.cs.bp.bpjs.internal.ExecutorServiceMaker;
import il.ac.bgu.cs.bp.bpjs.model.BProgramSyncSnapshot;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Takes a {@link BProgram}, and verifies that it does not run into false
* assertions or deadlock, given all possible event selections.
* Take care to use the appropriate {@link VisitedStateStore} for the
* {@link BProgram} being verified.
*
* States are scanned using a DFS.
*
* @author michael
*/
public class DfsBProgramVerifier {
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();
public final static long DEFAULT_MAX_TRACE = 1000;
/**
* Default number of iterations between invoking the progress listeners.
*/
public final static long DEFAULT_ITERATION_COUNT_GAP = 1000;
/**
* A listener to the progress of the DFS state scanning.
*/
public static interface ProgressListener {
void started(DfsBProgramVerifier v);
void iterationCount(long count, long statesHit, DfsBProgramVerifier v);
void maxTraceLengthHit(List trace, DfsBProgramVerifier v);
void done(DfsBProgramVerifier v);
}
private static class ViolatingCycleFoundException extends Exception{
final Violation v;
public ViolatingCycleFoundException(Violation v) {
this.v = v;
}
}
private long visitedEdgeCount;
private VisitedStateStore visited = new BThreadSnapshotVisitedStateStore();
private long maxTraceLength = DEFAULT_MAX_TRACE;
private final List currentPath = new ArrayList<>();
private Optional listenerOpt = Optional.empty();
private long iterationCountGap = DEFAULT_ITERATION_COUNT_GAP;
private BProgram currentBProgram;
private boolean debugMode = false;
private final Set inspections = new HashSet<>();
private ArrayExecutionTrace trace;
public VerificationResult verify(BProgram aBp) throws Exception {
currentBProgram = aBp;
visitedEdgeCount = 0;
currentPath.clear();
visited.clear();
trace = new ArrayExecutionTrace(currentBProgram);
// in case no verifications were specified, use the defauls set.
if ( inspections.isEmpty() ) {
inspections.addAll( ExecutionTraceInspections.DEFAULT_SET );
}
ExecutorService execSvc = ExecutorServiceMaker.makeWithName("DfsBProgramRunner-" + INSTANCE_COUNTER.incrementAndGet());
long start = System.currentTimeMillis();
listenerOpt.ifPresent(l -> l.started(this));
Violation vio = dfsUsingStack(new DfsTraversalNode(currentBProgram, currentBProgram.setup().start(execSvc), null), execSvc);
long end = System.currentTimeMillis();
listenerOpt.ifPresent(l -> l.done(this));
execSvc.shutdown();
return new VerificationResult(vio, end - start, visited.getVisitedStateCount(), visitedEdgeCount);
}
protected Violation dfsUsingStack(DfsTraversalNode aStartNode, ExecutorService execSvc) throws Exception {
long iterationCount = 0;
push(aStartNode);
while (!isPathEmpty()) {
if (debugMode) {
printStatus(iterationCount, Collections.unmodifiableList(currentPath));
}
DfsTraversalNode curNode = peek();
if (curNode != null) {
Optional res = inspections.stream()
.map(v->v.inspectTrace(trace))
.filter(o->o.isPresent()).map(Optional::get)
.findAny();
if ( res.isPresent() ) {
return res.get();
}
}
iterationCount++;
if (pathLength() == maxTraceLength) {
if (listenerOpt.isPresent()) {
listenerOpt.get().maxTraceLengthHit(currentPath, this);
}
// fold stack;
pop();
} else {
try {
DfsTraversalNode nextNode = getUnvisitedNextNode(curNode, execSvc);
if (nextNode == null) {
// fold stack, retry next iteration;
pop();
if (isDebugMode()) {
System.out.println("-pop!-");
}
} else {
// go deeper
if (isDebugMode()) {
System.out.println("-visiting: " + nextNode);
}
push(nextNode);
}
} catch (ViolatingCycleFoundException vcfe ) {
return vcfe.v;
}
}
if (iterationCount % iterationCountGap == 0 && listenerOpt.isPresent()) {
listenerOpt.get().iterationCount(iterationCount, visited.getVisitedStateCount(), this);
}
}
return null;
}
protected DfsTraversalNode getUnvisitedNextNode(DfsTraversalNode src, ExecutorService execSvc) throws ViolatingCycleFoundException, Exception {
while (src.getEventIterator().hasNext()) {
final BEvent nextEvent = src.getEventIterator().next();
DfsTraversalNode possibleNextNode = src.getNextNode(nextEvent, execSvc);
visitedEdgeCount++;
if (visited.isVisited(possibleNextNode.getSystemState()) ) {
// Found a possible cycle
BProgramSyncSnapshot pns = possibleNextNode.getSystemState();
for ( int idx=0; idx res = inspections.stream().map(i->i.inspectTrace(trace))
.filter(o->o.isPresent()).map(Optional::get)
.findAny();
if ( res.isPresent() ) {
throw new ViolatingCycleFoundException(res.get());
}
}
}
} else {
// advance to this newly discovered node
return possibleNextNode;
}
}
return null;
}
public void setMaxTraceLength(long maxTraceLength) {
this.maxTraceLength = maxTraceLength;
}
public long getMaxTraceLength() {
return maxTraceLength;
}
public void setVisitedNodeStore(VisitedStateStore aVisitedNodeStore) {
visited = aVisitedNodeStore;
}
public VisitedStateStore getVisitedNodeStore() {
return visited;
}
public void setProgressListener(ProgressListener pl) {
listenerOpt = Optional.of(pl);
}
public void setIterationCountGap(long iterationCountGap) {
this.iterationCountGap = iterationCountGap;
}
public long getIterationCountGap() {
return iterationCountGap;
}
public BProgram getCurrentBProgram() {
return currentBProgram;
}
void printStatus(long iteration, List path) {
System.out.println("Iteration " + iteration);
System.out.println(" visited: " + visited.getVisitedStateCount());
path.forEach(n -> System.out.println(" " + n.getLastEvent()));
}
private void push(DfsTraversalNode n) {
visited.store(n.getSystemState());
currentPath.add(n);
if ( trace.getStateCount() == 0 ) {
trace.push( n.getSystemState() );
} else {
trace.advance(n.getLastEvent(), n.getSystemState());
}
}
private void pop() {
currentPath.remove(currentPath.size() - 1);
trace.pop();
}
private int pathLength() {
return currentPath.size();
}
private boolean isPathEmpty() {
return pathLength() == 0;
}
private DfsTraversalNode peek() {
return isPathEmpty() ? null : currentPath.get(currentPath.size() - 1);
}
public boolean isDebugMode() {
return debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public void addInspection( ExecutionTraceInspection ins ) {
inspections.add(ins);
}
public Set getInspections() {
return inspections;
}
public boolean removeInspection( ExecutionTraceInspection ins ) {
return inspections.remove(ins);
}
}