// MS: we definitely need to cache all the results or use a proper graph structure TransitionGraph::TransitionPtrSet TransitionGraph::outEdges(const EState* estate) { ROSE_ASSERT(estate); if(getModeLTLDriven()) { ROSE_ASSERT(_analyzer); if(_outEdges[estate].size()==0) { ROSE_ASSERT(_analyzer); Analyzer::SubSolverResultType subSolverResult=_analyzer->subSolver(estate); EStateWorkList& deferedWorkList=subSolverResult.first; EStateSet& existingEStateSet=subSolverResult.second; EStatePtrSet succNodes; for(EStateWorkList::iterator i=deferedWorkList.begin(); i!=deferedWorkList.end(); ++i) { succNodes.insert(*i); } for(EStateSet::iterator i=existingEStateSet.begin(); i!=existingEStateSet.end(); ++i) { succNodes.insert(*i); } //cout<<"DEBUG: succ:"<<deferedWorkList.size()<<","<<existingEStateSet.size()<<":"<<succNodes.size()<<endl; for(EStatePtrSet::iterator j=succNodes.begin(); j!=succNodes.end(); ++j) { Edge newEdge(estate->label(),EDGE_PATH,(*j)->label()); Transition t(estate,newEdge,*j); add(t); } } //if(_outEdges[estate].size()>0) cout<<"DEBUG: #out-edges="<<_outEdges[estate].size()<<endl; } return _outEdges[estate]; }
EStatePtrSet TransitionGraph::succ(const EState* estate) { EStatePtrSet succNodes; TransitionPtrSet tset=outEdges(estate); for(TransitionPtrSet::iterator i=tset.begin(); i!=tset.end(); ++i) { succNodes.insert((*i)->target); } return succNodes; }
EStatePtrSet TransitionGraph::pred(const EState* estate) { EStatePtrSet predNodes; TransitionPtrSet tset=inEdges(estate); for(TransitionPtrSet::iterator i=tset.begin(); i!=tset.end(); ++i) { predNodes.insert((*i)->source); } return predNodes; }
vector<const EState*> CounterexampleAnalyzer::sortAbstractInputStates(vector<const EState*> v, EStatePtrSet abstractInputStates) { for (EStatePtrSet::iterator i=abstractInputStates.begin(); i!=abstractInputStates.end(); ++i) { PState* pstate = const_cast<PState*>( (*i)->pstate() ); int inVal = (*pstate)[_analyzer->globalVarIdByName("input")].getValue().getIntValue(); v[inVal - 1] = (*i); } return v; }
CEAnalysisStep CounterexampleAnalyzer::getSpuriousTransition(list<CeIoVal> partOfCeTrace, TransitionGraph* originalTraceGraph, const EState* compareFollowingHere, StateSets* statesPerCycleIndex) { assert(compareFollowingHere); const EState* currentState = compareFollowingHere; bool matchingState; CeIoVal realBehavior; int index = 1; CEAnalysisStep result; result.assertionCausingSpuriousInput = false; StateSets::iterator cycleIndexStates; if (statesPerCycleIndex) { //bookkeeping for analyzing the cyclic part of the counterexample cycleIndexStates = statesPerCycleIndex->begin(); } // iterate over part of the counterexample and compare its behavior step-by-step against the original program's trace for (list<CeIoVal>::iterator i = partOfCeTrace.begin(); i != partOfCeTrace.end(); i++) { matchingState = false; EStatePtrSet successors = originalTraceGraph->succ(currentState); // the trace graph should always contain enough states for the comparision assert(successors.size()>0); // retrieve the correct state at branches in the original program (branches may exist due to inherent loops) // note: assuming deterministic input/output programs for (EStatePtrSet::iterator succ = successors.begin(); succ != successors.end(); succ++) { realBehavior = eStateToCeIoVal(*succ); //cout << "DEBUG: realBehavior: " << ceIoValToString(realBehavior) << " ceTraceItem: " << ceIoValToString(*i) << endl; if (realBehavior == (*i)) { //no spurious behavior when following this path, so continue here currentState = (*succ); index++; matchingState = true; if (statesPerCycleIndex) { //check if the real state has already been seen pair<boost::unordered_set<const EState*>::iterator, bool> notYetSeen = cycleIndexStates->insert(currentState); if (notYetSeen.second == false) { //state encountered twice at the some counterexample index. cycle found --> real counterexample result.analysisResult = CE_TYPE_REAL; return result; } else { cycleIndexStates++; } } // special case. output-> failing assertion occurs during execution of the original program, leading to a spurious input symbol. // RefinementConstraints should use the failing assertion's condition to add new constraints, trying to enforce the assertion to fail // and thus eliminating the spurious input symbol. } else if (realBehavior.second == IO_TYPE_ERROR && i->second == IO_TYPE_INPUT) { result.assertionCausingSpuriousInput = true; result.failingAssertionInOriginal = (*succ)->label(); } } // if no matching transition was found, then the counterexample trace is spurious if (!matchingState) { result.analysisResult = CE_TYPE_SPURIOUS; result.spuriousIndexInCurrentPart = index; return result; } } IoType mostRecentOriginalIoType = (partOfCeTrace.rbegin())->second; determineAnalysisStepResult(result, mostRecentOriginalIoType, currentState, originalTraceGraph, index); return result; // partOfCeTrace could be successfully traversed on the originalTraceGraph }
Label CounterexampleAnalyzer::getFirstObservableSpuriousLabel(TransitionGraph* analyzedModel, PrefixAndCycle counterexample, int numberOfSteps) { boost::unordered_set<const EState*>* currentDepth = new boost::unordered_set<const EState*>(); boost::unordered_set<const EState*>* nextDepth = new boost::unordered_set<const EState*>(); currentDepth->insert(analyzedModel->getStartEState()); list<CeIoVal> prefix = counterexample.first; list<CeIoVal> cycle = counterexample.second; unsigned int prefixIndex = 0; unsigned int cycleIndex = 0; assert(prefix.size() > 0); // in case of an empty prefix, refine the initialization here list<CeIoVal>::iterator currentCeBehavior = prefix.begin(); bool inCycle = false; // traverse all paths in the analyzed model that match with the counterexample's behavior for (int i = 0; i < numberOfSteps; i++) { //traverse one more level for (boost::unordered_set<const EState*>::iterator k = currentDepth->begin(); k != currentDepth->end(); k++) { EStatePtrSet successors = analyzedModel->succ(*k); // add those successor states to "nextDepth" that match the desired behavior for (EStatePtrSet::iterator m = successors.begin(); m != successors.end(); m++) { if (eStateToCeIoVal(*m) == *currentCeBehavior) { nextDepth->insert(*m); } } } // swap currentDepth and nextDepth, then clear nextDepth boost::unordered_set<const EState*>* swapTemp = currentDepth; currentDepth = nextDepth; nextDepth = swapTemp; nextDepth->clear(); //update currentCeBehavior if (!inCycle) { if (prefixIndex < prefix.size() - 1) { currentCeBehavior++; prefixIndex++; } else { currentCeBehavior = cycle.begin(); inCycle = true; } } else { if (cycleIndex < cycle.size() - 1) { currentCeBehavior++; cycleIndex++; } else { currentCeBehavior = cycle.begin(); cycleIndex = 0; } } } // the counterexample itself comes from analyzing "anaylzedModel", there has to be at least one matching state assert (currentDepth->size() > 0); const EState* firstMatch = *(currentDepth->begin()); delete currentDepth; delete nextDepth; assert(firstMatch); return firstMatch->label(); }
// author: Marc Jasper, 2015. long TransitionGraph::numberOfObservableStates(bool includeIn, bool includeOut, bool includeErr) { long result = 0; EStatePtrSet allStates = estateSet(); for (EStatePtrSet::iterator i=allStates.begin(); i!=allStates.end(); ++i) { if ((includeIn && (*i)->io.isStdInIO()) || (includeOut && (*i)->io.isStdOutIO()) || (includeErr && ((*i)->io.isStdErrIO()||(*i)->io.isFailedAssertIO())) ) { result++; } } return result; }
EStatePtrSet CounterexampleAnalyzer::addAllPrefixOutputStates(EStatePtrSet& startAndOuputStatesPrefix, TransitionGraph* model) { EStatePtrSet allEStates = model->estateSet(); for (EStatePtrSet::iterator i=allEStates.begin(); i!=allEStates.end(); ++i) { //start state has already been added if ((*i)->io.isStdOutIO()) { if (! (*i)->isRersTopified(_analyzer->getVariableIdMapping())) { startAndOuputStatesPrefix.insert(*i); } } } return startAndOuputStatesPrefix; }
vector<bool> CounterexampleAnalyzer::hasFollowingInputStates(vector<bool> v, const EState*eState, TransitionGraph* model) { EStatePtrSet successors = model->succ(eState); for (EStatePtrSet::iterator k=successors.begin(); k!=successors.end(); ++k) { if ((*k)->io.isStdInIO()) { PState* pstate = const_cast<PState*>( (*k)->pstate() ); int inVal = (*pstate)[_analyzer->globalVarIdByName("input")].getValue().getIntValue(); v[inVal - 1] = true; }else { cout << "ERROR: CounterexampleAnalyzer::cegarPrefixAnalysisForLtl: successor of prefix output (or start) state is not an input state." << endl; assert(0); } } return v; }
vector<const EState*> CounterexampleAnalyzer::getFollowingInputStates(vector<const EState*> v, const EState* startEState, TransitionGraph* model) { EStatePtrSet firstInputStates = model->succ(startEState); for (EStatePtrSet::iterator i=firstInputStates.begin(); i!=firstInputStates.end(); ++i) { if ((*i)->io.isStdInIO()) { PState* pstate = const_cast<PState*>( (*i)->pstate() ); int inVal = (*pstate)[_analyzer->globalVarIdByName("input")].getValue().getIntValue(); v[inVal - 1] = (*i); } else { cout << "ERROR: CounterexampleAnalyzer::cegarPrefixAnalysisForLtl: successor of initial model's start state is not an input state." << endl; assert(0); } } return v; }
list<pair<const EState*, int> > CounterexampleAnalyzer::removeTraceLeadingToErrorState(const EState* errorState, TransitionGraph* stg) { assert(errorState->io.isFailedAssertIO() || errorState->io.isStdErrIO() ); list<pair<const EState*, int> > erroneousTransitions; PState* pstate = const_cast<PState*>( errorState->pstate() ); int latestInputVal = (*pstate)[_analyzer->globalVarIdByName("input")].getValue().getIntValue(); //eliminate the error state const EState* eliminateThisOne = errorState; EStatePtrSet preds = stg->pred(eliminateThisOne); assert (stg->succ(eliminateThisOne).size() == 0); // error state should have no successors. assert (preds.size() == 1); // error states are immediately removed after being discovered. const EState* currentState = *(preds.begin()); stg->eliminateEState(eliminateThisOne); //in the case of new input->output->error behavior, delete the output too if (currentState->io.isStdOutIO()) { eliminateThisOne = currentState; preds = stg->pred(currentState); assert (preds.size() == 1); currentState = *(preds.begin()); stg->eliminateEState(eliminateThisOne); } //also delete the input state right before assert (currentState->io.isStdInIO()); eliminateThisOne = currentState; preds = stg->pred(currentState); // exclude all edges leading to the input state right before the error state for future tracing attempts // (due to the reduction to observable behavior, even a single execution trace can lead to multiple predecessors) for (EStatePtrSet::iterator i=preds.begin(); i!= preds.end(); ++i) { erroneousTransitions.push_back(pair<const EState*, int>(*i, latestInputVal)); } stg->eliminateEState(eliminateThisOne); return erroneousTransitions; }
pair<EStatePtrSet, EStatePtrSet> CounterexampleAnalyzer::getConcreteOutputAndAbstractInput(TransitionGraph* model) { EStatePtrSet allEStates=model->estateSet(); EStatePtrSet concreteOutputStates; EStatePtrSet abstractInputStates; concreteOutputStates.insert(model->getStartEState()); // the start state has following input states just like output states do. for(EStatePtrSet::iterator i=allEStates.begin(); i!=allEStates.end(); ++i) { if ((*i)->isRersTopified(_analyzer->getVariableIdMapping())) { if ((*i)->io.isStdInIO()) { abstractInputStates.insert(*i); } } else { if ((*i)->io.isStdOutIO()) { concreteOutputStates.insert(*i); } } } return pair<EStatePtrSet, EStatePtrSet> (concreteOutputStates, abstractInputStates); }
void CounterexampleAnalyzer::determineAnalysisStepResult(CEAnalysisStep& result, IoType mostRecentOriginalIoType, const EState* currentState, TransitionGraph* originalTraceGraph, int index) { result.analysisResult = CE_TYPE_UNKNOWN; result.mostRecentStateRealTrace = currentState; result.continueTracingOriginal = true; // set and return the result // Takes care of the corner case that error states after the lastly assessed input symbol render // further tracing of the original program's path impossible. if (mostRecentOriginalIoType == IO_TYPE_INPUT) { EStatePtrSet successors = originalTraceGraph->succ(currentState); assert(successors.size() == 1); //(input-)determinism of the original program const EState * nextEState = *(successors.begin()); assert(nextEState); if (nextEState->io.isStdErrIO() || nextEState->io.isFailedAssertIO()) { // (*) see below (the first symbol of the next partOfCeTrace has to be the spurious transition here) result.analysisResult = CE_TYPE_SPURIOUS; result.spuriousIndexInCurrentPart = index; } else if (nextEState->io.isStdOutIO()) { successors = originalTraceGraph->succ(nextEState); for (EStatePtrSet::iterator i = successors.begin(); i != successors.end(); ++i) { const EState* nextEState = *i; if (nextEState->io.isStdErrIO() || nextEState->io.isFailedAssertIO()) { // (*) see below result.continueTracingOriginal = false; } } } } else if (mostRecentOriginalIoType == IO_TYPE_OUTPUT) { EStatePtrSet successors = originalTraceGraph->succ(currentState); for (EStatePtrSet::iterator i = successors.begin(); i != successors.end(); ++i) { const EState* nextEState = *i; if (nextEState->io.isStdErrIO() || nextEState->io.isFailedAssertIO()) { // (*) see below result.continueTracingOriginal = false; } } } // (*) there is a not yet seen error state. Because every state except from successors of the last input state of the trace have // been looked at already, the cycle part of the CE contains no input state before the error state. RERS programs need to // contain at least one input symbol in the cycle part of a counterexample --> this counterexample is spurious. }
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; }