void TransitionGraphReducer::reduceStgToStatesSatisfying(function<bool(const EState*)> predicate) { if (_stg->size() == 0) { return; } // no in-place reduction because paths in between states that satisfy the predicate might be shared TransitionGraph* reducedStg = new TransitionGraph(); reducedStg->setStartLabel(_stg->getStartLabel()); reducedStg->setIsPrecise(_stg->isPrecise()); reducedStg->setIsComplete(_stg->isComplete()); // init list<const EState*> worklist; unordered_set<const EState*> visited; worklist.push_back(_stg->getStartEState()); visited.insert(_stg->getStartEState()); // traverse the entire _stg while (!worklist.empty()) { const EState* current = *worklist.begin(); ROSE_ASSERT(current); worklist.pop_front(); ROSE_ASSERT(predicate(current) || current == _stg->getStartEState()); // similar to Analyzer's "subSolver" list<const EState*> successors = successorsOfStateSatisfying(current, predicate); for (list<const EState*>::iterator i=successors.begin(); i!= successors.end(); ++i) { if (visited.find(*i) == visited.end()) { worklist.push_back(*i); visited.insert(*i); } Edge* newEdge = new Edge(current->label(),EDGE_PATH,(*i)->label()); reducedStg->add(Transition(current, *newEdge, *i)); } } // replace old stg (local variable of analyzer, stack frame) with reduced one *_stg = *reducedStg; // "garbage collection": Remove all states from _states that were bypassed during the reduction // (cannot simply replace the set because of potential states in the analyzer's worklist) EStateSet::iterator i=_states->begin(); while (i!=_states->end()) { if (visited.find(*i) == visited.end()) { i = _states->erase(i); } else { ++i; } } }
PropertyValueTable* CounterexampleAnalyzer::cegarPrefixAnalysisForLtl(int property, SpotConnection& spotConnection, set<int> ltlInAlphabet, set<int> ltlOutAlphabet) { // visualizer for in-depth model outputs (.dot files) Visualizer visualizer(_analyzer->getLabeler(),_analyzer->getVariableIdMapping(), _analyzer->getFlow(),_analyzer->getPStateSet(),_analyzer->getEStateSet(),_analyzer->getTransitionGraph()); string vizFilenamePrefix = ""; if(args.count("viz-cegpra-detailed")) { vizFilenamePrefix=args["viz-cegpra-detailed"].as<string>(); string filename = vizFilenamePrefix + "_cegpra_init.dot"; writeDotGraphToDisk(filename, visualizer); } // OVERVIEW // (0) check if the initial model already satsifies the property // (0.5) initialization // (while (property != satisfied)) do // (1) Disconnect the concrete prefix (initially the start state) from the over-approx. part of the model // (2) Anaylze the most recent counterexample while adding the trace of the original program to the prefix part of the model // If the counterexample is real: reconnect once more to determine the model's size, ESCAPE the loop and return results // (3) Reconnect both parts of the model // (4) Check the property on the now slightly refined model // od // (5) return results; cout << "STATUS: CEGPRA is now analyzing LTL property " << property << "..." << endl; if (_csvOutput) { (*_csvOutput) << endl << property << ","; } TransitionGraph* model = _analyzer->getTransitionGraph(); assert(model->isComplete()); // (0) check if the given property already holds on the initial over-approximated model PropertyValueTable* currentResults = spotConnection.getLtlResults(); if (currentResults->getPropertyValue(property) != PROPERTY_VALUE_UNKNOWN) { cout << "STATUS: property " << property << " was already analyzed. CEGAR analysis will not be started." << endl; return currentResults; } spotConnection.checkSingleProperty(property, *model, ltlInAlphabet, ltlOutAlphabet, true, true); currentResults = spotConnection.getLtlResults(); // (0.5) prepare for the continuous tracing of concrete states (will become the prefix of a refined abstract model) // store connectors in the over-approx. part of the model (single row of input states in the initial "topified" model) const EState* startEState = model->getStartEState(); pair<EStatePtrSet, EStatePtrSet> concOutputAndAbstrInput = getConcreteOutputAndAbstractInput(model); EStatePtrSet startAndOuputStatesPrefix = concOutputAndAbstrInput.first; vector<const EState*> firstInputOverApprox(ltlInAlphabet.size()); firstInputOverApprox = sortAbstractInputStates(firstInputOverApprox, concOutputAndAbstrInput.second); int loopCount = 0; bool falsified = false; bool verified = true; // the usual case for the loop below to terminate is a verified property. string ce = "no counterexample yet"; // as long as the property is not satisfiable yet, refine by enlarging the prefix of concrete states according to counterexamples while (currentResults->getPropertyValue(property) != PROPERTY_VALUE_YES) { if (_maxCounterexamples > -1 && (loopCount + 1) > _maxCounterexamples) { verified = false; spotConnection.resetLtlResults(property); break; } loopCount++; if (loopCount % 50 == 0) { cout << "STATUS: " << loopCount << " counterexamples analyzed. most recent counterexample: " << endl; cout << ce << endl; } // (1) disconnect prefix and over-approx. part of the model model->setIsComplete(false); for (unsigned int i = 0; i < firstInputOverApprox.size(); i++) { TransitionPtrSet connectionsToPrefix = model->inEdges(firstInputOverApprox[i]); for (TransitionPtrSet::iterator k=connectionsToPrefix.begin(); k!=connectionsToPrefix.end(); ++k) { // check if predeccesor at the source of that transition is a concrete (prefix) state if ( !(*k)->source->isRersTopified(_analyzer->getVariableIdMapping()) ) { model->erase(**k); } } } model->setIsPrecise(true); if(args.count("viz-cegpra-detailed")) { stringstream filenameStream; filenameStream << vizFilenamePrefix << "cegpra_afterDisconnect_i" << loopCount << ".dot"; writeDotGraphToDisk(filenameStream.str(), visualizer); } // (2) add a trace to the prefix according to the most recent counterexample. Analyze the counterexample while adding the trace. ce = currentResults->getCounterexample(property); //cout << "STATUS: counterexample: " << ce << endl; CEAnalysisResult ceaResult = analyzeCounterexample(ce, startEState, false, false); if (ceaResult.analysisResult == CE_TYPE_REAL) { // still reconnect the concrete prefix with the over-approx. part of the model (step (3)) in order to report the size. falsified = true; verified = false; } else if (ceaResult.analysisResult == CE_TYPE_SPURIOUS) { if(!boolOptions["keep-error-states"]) { // remove a trace leading to an error state and mark the branches to it (do not reconnect in phase 3) removeAndMarkErroneousBranches(model); } //the trace eliminating the spurious counterexample (maybe including a few extra states) was added to the prefix during analysis. // --> nothing to do here } else { assert(0); //counterexample analysis not successfully completed } if(args.count("viz-cegpra-detailed")) { stringstream filenameStream; filenameStream << vizFilenamePrefix << "cegpra_afterCECheck_i" << loopCount << ".dot"; writeDotGraphToDisk(filenameStream.str(), visualizer); } // (3) reconnect both parts of the model model->setIsPrecise(false); //update set of output states (plus start state) in the precise prefix addAllPrefixOutputStates(startAndOuputStatesPrefix, model); for (set<const EState*>::iterator i=startAndOuputStatesPrefix.begin(); i!=startAndOuputStatesPrefix.end(); ++i) { vector<bool> inputSuccessors(ltlInAlphabet.size(), false); if(!boolOptions["keep-error-states"]) { inputSuccessors = setErrorBranches(inputSuccessors, *i); } // determine which input states exist as successors in the prefix inputSuccessors = hasFollowingInputStates(inputSuccessors, *i, model); //connect with the approx. part of the model for all input values not found among the successor states for (unsigned int k = 0; k < inputSuccessors.size(); k++) { if (!inputSuccessors[k]) { Edge newEdge; model->add(Transition((*i), newEdge, firstInputOverApprox[k])); } } } model->setIsComplete(true); if(args.count("viz-cegpra-detailed")) { stringstream filenameStream; filenameStream << vizFilenamePrefix << "cegpra_afterReconnect_i" << loopCount << ".dot"; writeDotGraphToDisk(filenameStream.str(), visualizer); } // if falsified: after reconnecting, leave the analysis loop, report size of the model and return the results if (falsified) { break; } // (4) check if the property holds on the refined model spotConnection.resetLtlResults(property); spotConnection.checkSingleProperty(property, *model, ltlInAlphabet, ltlOutAlphabet, true, true); currentResults = spotConnection.getLtlResults(); } // (5) check all properties using the current model and return the result spotConnection.checkLtlProperties(*model, ltlInAlphabet, ltlOutAlphabet, true, false); currentResults = spotConnection.getLtlResults(); printStgSizeAndCeCount(model, loopCount, property); if (_csvOutput) { if (verified && !falsified) (*_csvOutput) << "y,"; if (!verified && falsified) (*_csvOutput) << "n,"; if (!verified && !falsified) (*_csvOutput) << "?,"; if (verified && falsified) { cout << "ERROR: property can not be both verified and falsified. " << endl; assert(0); } (*_csvOutput) << currentResults->entriesWithValue(PROPERTY_VALUE_YES)<<","; (*_csvOutput) << currentResults->entriesWithValue(PROPERTY_VALUE_NO)<<","; (*_csvOutput) << currentResults->entriesWithValue(PROPERTY_VALUE_UNKNOWN); } return currentResults; }
CEAnalysisResult CounterexampleAnalyzer::analyzeCounterexample(string counterexample, const EState* startState, bool returnSpuriousLabel, bool resetAnalyzerData) { // OVERVIEW // (0) compare counterexample(CE) prefix with real trace // (1) initialize one hashset of seen EStates for each index of the CE cycle (bookkeeping) // (2) while (unknown whether or not CE is spurious) // (2.1) compare one iteration of the CE's cycle part step-by-step with the original program // (2.2) one step: If (step is spurious) then determine the corresponding label in the approximated model and set "spuriousLabel". return "spurious" // Else if (step is not spurious and cycle found) then return "real" // Else store step in hashset (bookkeeping) and continue with the next step // (3) return analysis result (including label for spurious transition's target, if existing) //cout << "DEBUG: counterexample: " << counterexample << endl; CEAnalysisResult result; result.analysisResult = CE_TYPE_UNKNOWN; PrefixAndCycle ceTrace = ceStringToInOutVals(counterexample); int stepsToSpuriousLabel = 0; if (returnSpuriousLabel) { //save abstract model in order to be able to traverse it later (in the case of a spurious CE) _analyzer->reduceToObservableBehavior(); _analyzer->storeStgBackup(); } // (0) compare the prefix with traces on the original program list<CeIoVal> cePrefix = ceTrace.first; //run prefix on original program EState* startEState; if (startState) { //workaround for the start state not being part of the STG without having transitions startEState = const_cast<EState*>(startState); } else { startEState = const_cast<EState*>((_analyzer->getTransitionGraph())->getStartEState()); } _analyzer->setAnalyzerToSolver8(startEState, resetAnalyzerData); setInputSequence(cePrefix); _analyzer->runSolver(); _analyzer->reduceToObservableBehavior(); TransitionGraph* traceGraph = _analyzer->getTransitionGraph(); // compare const EState * compareFollowingHereNext = traceGraph->getStartEState(); CEAnalysisStep currentResult = getSpuriousTransition(cePrefix, traceGraph, compareFollowingHereNext); if (currentResult.analysisResult == CE_TYPE_SPURIOUS) { // the number of steps to the spurious label will be updated at the end of this function } else if (currentResult.analysisResult == CE_TYPE_UNKNOWN) { // only continue if prefix was not spurious list<CeIoVal> ceCycle = ceTrace.second; stepsToSpuriousLabel = cePrefix.size(); // (1) initialize one hashset per index in the cyclic part of the CE (bookkeeping) StateSets* statesPerCycleIndex = new StateSets(); for (unsigned int i = 0; i < ceCycle.size(); i++) { boost::unordered_set<const EState*> newSet; statesPerCycleIndex->push_back(newSet); } //(2) while (unknown whether or not CE is spurious) while (currentResult.analysisResult == CE_TYPE_UNKNOWN) { if (currentResult.continueTracingOriginal) { //run one cycle iteration on the original program EState* continueTracingFromHere = const_cast<EState*>(_analyzer->getEstateBeforeMissingInput()); assert(continueTracingFromHere); setInputSequence(ceCycle); _analyzer->continueAnalysisFrom(continueTracingFromHere); _analyzer->reduceToObservableBehavior(); traceGraph = _analyzer->getTransitionGraph(); } //compare compareFollowingHereNext = currentResult.mostRecentStateRealTrace; currentResult = getSpuriousTransition(ceCycle, traceGraph, compareFollowingHereNext, statesPerCycleIndex); if (currentResult.analysisResult == CE_TYPE_UNKNOWN) { stepsToSpuriousLabel += ceCycle.size(); } } delete statesPerCycleIndex; statesPerCycleIndex = NULL; } // (3) process and return result if (returnSpuriousLabel) { _analyzer->swapStgWithBackup(); } if (currentResult.analysisResult == CE_TYPE_SPURIOUS) { result.analysisResult = CE_TYPE_SPURIOUS; //determine final number of steps to first observable spurious transition // special case for an output followed by a failing assertion in the original program // (The approximated model trace does not contain the assertion. Refinement will use the assertion's guarding condition.) if (currentResult.assertionCausingSpuriousInput) { if (returnSpuriousLabel) { result.spuriousTargetLabel = currentResult.failingAssertionInOriginal; } } else { stepsToSpuriousLabel += currentResult.spuriousIndexInCurrentPart; if (returnSpuriousLabel) { result.spuriousTargetLabel = getFirstObservableSpuriousLabel(_analyzer->getTransitionGraph(), ceTrace, stepsToSpuriousLabel); } } } else if (currentResult.analysisResult == CE_TYPE_REAL) { result.analysisResult = CE_TYPE_REAL; } else { cout << "ERROR: counterexample should be either spurious or real." << endl; assert(0); } return result; }