From 9509b795c7493d7b351dc25855a6a82cd392deb1 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Fri, 22 Aug 2014 18:21:18 +0200 Subject: Moved InterpreterIssues to own source file --- src/uscxml/CMakeLists.txt | 2 +- src/uscxml/Interpreter.cpp | 519 +-------------------------- src/uscxml/Interpreter.h | 21 +- src/uscxml/debug/InterpreterIssue.cpp | 656 ++++++++++++++++++++++++++++++++++ src/uscxml/debug/InterpreterIssue.h | 54 +++ test/src/test-issue-reporting.cpp | 36 ++ 6 files changed, 761 insertions(+), 527 deletions(-) create mode 100644 src/uscxml/debug/InterpreterIssue.cpp create mode 100644 src/uscxml/debug/InterpreterIssue.h diff --git a/src/uscxml/CMakeLists.txt b/src/uscxml/CMakeLists.txt index d74f512..51ee4ea 100644 --- a/src/uscxml/CMakeLists.txt +++ b/src/uscxml/CMakeLists.txt @@ -84,7 +84,7 @@ list (APPEND USCXML_FILES ${USCXML_PLUGINS}) if (BUILD_AS_PLUGINS) list (APPEND USCXML_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/plugins) - file(GLOB PLUMA + file(GLOB PLUMA plugins/Pluma/*.cpp plugins/Pluma/*.h plugins/*.cpp diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 370bef0..b9e7145 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -713,513 +713,8 @@ void InterpreterImpl::reset() { setInterpreterState(USCXML_INSTANTIATED); } -InterpreterIssue::InterpreterIssue(const std::string& msg, Arabica::DOM::Node node, IssueSeverity severity) : message(msg), node(node), severity(severity) { - if (node) - xPath = DOMUtils::xPathForNode(node); -} - std::list InterpreterImpl::validate() { - // some things we need to prepare first - if (_factory == NULL) - _factory = Factory::getInstance(); - setupDOM(); - - std::list issues; - - if (!_scxml) { - InterpreterIssue issue("No SCXML element to be found", Node(), InterpreterIssue::USCXML_ISSUE_FATAL); - issues.push_back(issue); - return issues; - } - - _cachedStates.clear(); - - NodeSet scxmls = filterChildElements(_nsInfo.xmlNSPrefix + "scxml", _scxml, true); - scxmls.push_back(_scxml); - - NodeSet states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml, true); - NodeSet parallels = filterChildElements(_nsInfo.xmlNSPrefix + "parallel", _scxml, true); - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); - NodeSet initials = filterChildElements(_nsInfo.xmlNSPrefix + "initial", _scxml, true); - NodeSet finals = filterChildElements(_nsInfo.xmlNSPrefix + "final", _scxml, true); - NodeSet onEntries = filterChildElements(_nsInfo.xmlNSPrefix + "onentry", _scxml, true); - NodeSet onExits = filterChildElements(_nsInfo.xmlNSPrefix + "onexit", _scxml, true); - NodeSet histories = filterChildElements(_nsInfo.xmlNSPrefix + "history", _scxml, true); - - NodeSet raises = filterChildElements(_nsInfo.xmlNSPrefix + "raise", _scxml, true); - NodeSet ifs = filterChildElements(_nsInfo.xmlNSPrefix + "if", _scxml, true); - NodeSet elseIfs = filterChildElements(_nsInfo.xmlNSPrefix + "elseif", _scxml, true); - NodeSet elses = filterChildElements(_nsInfo.xmlNSPrefix + "else", _scxml, true); - NodeSet foreachs = filterChildElements(_nsInfo.xmlNSPrefix + "foreach", _scxml, true); - NodeSet logs = filterChildElements(_nsInfo.xmlNSPrefix + "log", _scxml, true); - - NodeSet dataModels = filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _scxml, true); - NodeSet datas = filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true); - NodeSet assigns = filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true); - NodeSet doneDatas = filterChildElements(_nsInfo.xmlNSPrefix + "donedata", _scxml, true); - NodeSet contents = filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true); - NodeSet params = filterChildElements(_nsInfo.xmlNSPrefix + "param", _scxml, true); - NodeSet scripts = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml, true); - - NodeSet sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true); - NodeSet cancels = filterChildElements(_nsInfo.xmlNSPrefix + "cancel", _scxml, true); - NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); - NodeSet finalizes = filterChildElements(_nsInfo.xmlNSPrefix + "finalize", _scxml, true); - - NodeSet allStates; - allStates.push_back(states); - allStates.push_back(parallels); - allStates.push_back(histories); - allStates.push_back(finals); - - NodeSet allExecContents; - allExecContents.push_back(raises); - allExecContents.push_back(ifs); - allExecContents.push_back(elseIfs); - allExecContents.push_back(elses); - allExecContents.push_back(foreachs); - allExecContents.push_back(logs); - allExecContents.push_back(sends); - allExecContents.push_back(assigns); - allExecContents.push_back(scripts); - allExecContents.push_back(cancels); - - NodeSet allElements; - allElements.push_back(allStates); - allElements.push_back(allExecContents); - allElements.push_back(transitions); - allElements.push_back(initials); - allElements.push_back(onEntries); - allElements.push_back(onExits); - allElements.push_back(dataModels); - allElements.push_back(datas); - allElements.push_back(doneDatas); - allElements.push_back(contents); - allElements.push_back(params); - allElements.push_back(invokes); - allElements.push_back(finalizes); - - - std::set execContentSet; - execContentSet.insert("raise"); - execContentSet.insert("if"); - execContentSet.insert("elseif"); - execContentSet.insert("else"); - execContentSet.insert("foreach"); - execContentSet.insert("log"); - execContentSet.insert("send"); - execContentSet.insert("assign"); - execContentSet.insert("script"); - execContentSet.insert("cancel"); - - std::map > validChildren; - validChildren["scxml"].insert("state"); - validChildren["scxml"].insert("parallel"); - validChildren["scxml"].insert("final"); - validChildren["scxml"].insert("datamodel"); - validChildren["scxml"].insert("script"); - - validChildren["state"].insert("onentry"); - validChildren["state"].insert("onexit"); - validChildren["state"].insert("transition"); - validChildren["state"].insert("initial"); - validChildren["state"].insert("state"); - validChildren["state"].insert("parallel"); - validChildren["state"].insert("final"); - validChildren["state"].insert("history"); - validChildren["state"].insert("datamodel"); - validChildren["state"].insert("invoke"); - - validChildren["parallel"].insert("onentry"); - validChildren["parallel"].insert("onexit"); - validChildren["parallel"].insert("transition"); - validChildren["parallel"].insert("state"); - validChildren["parallel"].insert("parallel"); - validChildren["parallel"].insert("history"); - validChildren["parallel"].insert("datamodel"); - validChildren["parallel"].insert("invoke"); - - validChildren["transition"] = execContentSet; - validChildren["onentry"] = execContentSet; - validChildren["onexit"] = execContentSet; - validChildren["finalize"] = execContentSet; - - validChildren["if"] = execContentSet; - validChildren["elseif"] = execContentSet; - validChildren["else"] = execContentSet; - validChildren["foreach"] = execContentSet; - - validChildren["initial"].insert("transition"); - validChildren["history"].insert("transition"); - - validChildren["final"].insert("onentry"); - validChildren["final"].insert("onexit"); - validChildren["final"].insert("donedata"); - - validChildren["datamodel"].insert("data"); - - validChildren["donedata"].insert("content"); - validChildren["donedata"].insert("param"); - - validChildren["send"].insert("content"); - validChildren["send"].insert("param"); - - validChildren["invoke"].insert("content"); - validChildren["invoke"].insert("param"); - validChildren["invoke"].insert("finalize"); - - std::map > validParents; - for (std::map >::iterator parentIter = validChildren.begin(); parentIter != validChildren.end(); parentIter++) { - for (std::set::iterator childIter = parentIter->second.begin(); childIter != parentIter->second.end(); childIter++) { - validParents[*childIter].insert(parentIter->first); - } - } - - for (int i = 0; i < allStates.size(); i++) { - Element state = Element(allStates[i]); - - if (isMember(state, finals) && !HAS_ATTR(state, "id")) // id is not required for finals - continue; - - // check for existance of id attribute - if (!HAS_ATTR(state, "id")) { - issues.push_back(InterpreterIssue("State has no 'id' attribute", state, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - std::string stateId = ATTR(state, "id"); - - // check for uniqueness of id attribute - if (_cachedStates.find(stateId) != _cachedStates.end()) { - issues.push_back(InterpreterIssue("Duplicate state with id '" + stateId + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - _cachedStates[ATTR(state, "id")] = state; - } - - for (int i = 0; i < transitions.size(); i++) { - Element transition = Element(transitions[i]); - - // check for valid target - std::list targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target")); - for (std::list::iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { - if (_cachedStates.find(*targetIter) == _cachedStates.end()) { - issues.push_back(InterpreterIssue("Transition has non-existant target state with id '" + *targetIter + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - } - } - - // check for redundancy of transition - for (int i = 0; i < allStates.size(); i++) { - Element state = Element(allStates[i]); - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", state, false); - - transitions.to_document_order(); - - for (int j = 1; j < transitions.size(); j++) { - Element transition = Element(transitions[j]); - for (int k = 0; k < j; k++) { - Element earlierTransition = Element(transitions[k]); - - // will the earlier transition always be enabled when the later is? - if (!HAS_ATTR(earlierTransition, "cond")) { - // earlier transition has no condition -> check event descriptor - if (!HAS_ATTR(earlierTransition, "event")) { - // earlier transition is eventless - issues.push_back(InterpreterIssue("Transition can never be optimally enabled", transition, InterpreterIssue::USCXML_ISSUE_INFO)); - goto NEXT_TRANSITION; - - } else if (HAS_ATTR(transition, "event")) { - // does the earlier transition match all our events? - std::list events = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "event")); - - bool allMatched = true; - for (std::list::iterator eventIter = events.begin(); eventIter != events.end(); eventIter++) { - if (!nameMatch(ATTR(earlierTransition, "event"), *eventIter)) { - allMatched = false; - break; - } - } - - if (allMatched) { - issues.push_back(InterpreterIssue("Transition can never be optimally enabled", transition, InterpreterIssue::USCXML_ISSUE_INFO)); - goto NEXT_TRANSITION; - } - } - } - } - NEXT_TRANSITION:; - } - } - - // check for valid initial attribute - { - NodeSet withInitialAttr; - withInitialAttr.push_back(allStates); - withInitialAttr.push_back(_scxml); - - for (int i = 0; i < withInitialAttr.size(); i++) { - Element state = Element(withInitialAttr[i]); - if (HAS_ATTR(state, "initial")) { - std::list intials = InterpreterImpl::tokenizeIdRefs(ATTR(state, "initial")); - for (std::list::iterator initIter = intials.begin(); initIter != intials.end(); initIter++) { - if (_cachedStates.find(*initIter) == _cachedStates.end()) { - issues.push_back(InterpreterIssue("Initial attribute has invalid target state with id '" + *initIter + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - } - } - } - } - - // check that all invokers exists - { - for (int i = 0; i < invokes.size(); i++) { - Element invoke = Element(invokes[i]); - if (HAS_ATTR(invoke, "type") && !_factory->hasInvoker(ATTR(invoke, "type"))) { - issues.push_back(InterpreterIssue("Invoke with unknown type '" + ATTR(invoke, "type") + "'", invoke, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - } - } - - // check that all io processors exists - { - for (int i = 0; i < sends.size(); i++) { - Element send = Element(sends[i]); - if (HAS_ATTR(send, "type") && !_factory->hasIOProcessor(ATTR(send, "type"))) { - issues.push_back(InterpreterIssue("Send to unknown IO Processor '" + ATTR(send, "type") + "'", send, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - } - } - - // check that all custom executable content is known - { - NodeSet allExecContentContainers; - allExecContentContainers.push_back(onEntries); - allExecContentContainers.push_back(onExits); - allExecContentContainers.push_back(transitions); - allExecContentContainers.push_back(finalizes); - - for (int i = 0; i < allExecContentContainers.size(); i++) { - Element block = Element(allExecContentContainers[i]); - NodeSet execContents = filterChildType(Node_base::ELEMENT_NODE, block); - for (int j = 0; j < execContents.size(); j++) { - Element execContent = Element(execContents[j]); - // SCXML specific executable content, always available - if (isMember(execContent, allExecContents)) { - continue; - } - if (!_factory->hasExecutableContent(execContent.getLocalName(), execContent.getNamespaceURI())) { - issues.push_back(InterpreterIssue("Executable content element '" + execContent.getLocalName() + "' in namespace '" + execContent.getNamespaceURI() + "' unknown", execContent, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; - } - } - } - } - - // check that all SCXML elements have valid parents - { - for (int i = 0; i < allElements.size(); i++) { - Element element = Element(allElements[i]); - std::string localName = LOCALNAME(element); - if (!element.getParentNode() || element.getParentNode().getNodeType() != Node_base::ELEMENT_NODE) { - issues.push_back(InterpreterIssue("Parent of " + localName + " is no element", element, InterpreterIssue::USCXML_ISSUE_INFO)); - continue; - } - - Element parent = Element(element.getParentNode()); - std::string parentName = LOCALNAME(parent); - - if (validParents[localName].find(parentName) == validParents[localName].end()) { - issues.push_back(InterpreterIssue("Element " + parentName + " can be no parent of " + localName, element, InterpreterIssue::USCXML_ISSUE_INFO)); - continue; - } - } - } - - - // check that the datamodel is known - if (HAS_ATTR(_scxml, "datamodel")) { - if (!_factory->hasDataModel(ATTR(_scxml, "datamodel"))) { - issues.push_back(InterpreterIssue("SCXML document requires unknown datamodel '" + ATTR(_scxml, "datamodel") + "'", _scxml, InterpreterIssue::USCXML_ISSUE_FATAL)); - - // we cannot even check the rest as we require a datamodel - return issues; - } - } - - bool instantiatedDataModel = false; - // instantiate datamodel if not explicitly set - if (!_dataModel) { - if (HAS_ATTR(_scxml, "datamodel")) { - // might throw - _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), this); - instantiatedDataModel = true; - } else { - _dataModel = _factory->createDataModel("null", this); - } - } - - - // test all scripts for valid syntax - { - for (int i = 0; i < scripts.size(); i++) { - Element script = Element(scripts[i]); - - if (script.hasChildNodes()) { - // search for the text node with the actual script - std::string scriptContent; - for (Node child = script.getFirstChild(); child; child = child.getNextSibling()) { - if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) - scriptContent += child.getNodeValue(); - } - - if (!_dataModel.isValidSyntax(scriptContent)) { - issues.push_back(InterpreterIssue("Syntax error in script", script, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - } - } - - // test the various attributes with datamodel expressions for valid syntax - { - NodeSet withCondAttrs; - withCondAttrs.push_back(transitions); - withCondAttrs.push_back(ifs); - withCondAttrs.push_back(elseIfs); - - for (int i = 0; i < withCondAttrs.size(); i++) { - Element condAttr = Element(withCondAttrs[i]); - if (HAS_ATTR(condAttr, "cond")) { - if (!_dataModel.isValidSyntax(ATTR(condAttr, "cond"))) { - issues.push_back(InterpreterIssue("Syntax error in cond attribute", condAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - } - } - - { - NodeSet withExprAttrs; - withExprAttrs.push_back(logs); - withExprAttrs.push_back(datas); - withExprAttrs.push_back(assigns); - withExprAttrs.push_back(contents); - withExprAttrs.push_back(params); - - for (int i = 0; i < withExprAttrs.size(); i++) { - Element withExprAttr = Element(withExprAttrs[i]); - if (HAS_ATTR(withExprAttr, "expr")) { - if (isMember(withExprAttr, datas) || isMember(withExprAttr, assigns)) { - if (!_dataModel.isValidSyntax("foo = " + ATTR(withExprAttr, "expr"))) { // TODO: this is ECMAScripty! - issues.push_back(InterpreterIssue("Syntax error in expr attribute", withExprAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } else { - if (!_dataModel.isValidSyntax(ATTR(withExprAttr, "expr"))) { - issues.push_back(InterpreterIssue("Syntax error in expr attribute", withExprAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - } - } - } - - { - for (int i = 0; i < foreachs.size(); i++) { - Element foreach = Element(foreachs[i]); - if (HAS_ATTR(foreach, "array")) { - if (!_dataModel.isValidSyntax(ATTR(foreach, "array"))) { - issues.push_back(InterpreterIssue("Syntax error in array attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(foreach, "item")) { - if (!_dataModel.isValidSyntax(ATTR(foreach, "item"))) { - issues.push_back(InterpreterIssue("Syntax error in item attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(foreach, "index")) { - if (!_dataModel.isValidSyntax(ATTR(foreach, "index"))) { - issues.push_back(InterpreterIssue("Syntax error in index attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - } - } - - { - for (int i = 0; i < sends.size(); i++) { - Element send = Element(sends[i]); - if (HAS_ATTR(send, "eventexpr")) { - if (!_dataModel.isValidSyntax(ATTR(send, "eventexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in eventexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(send, "targetexpr")) { - if (!_dataModel.isValidSyntax(ATTR(send, "targetexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in targetexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(send, "typeexpr")) { - if (!_dataModel.isValidSyntax(ATTR(send, "typeexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in typeexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(send, "idlocation")) { - if (!_dataModel.isValidSyntax(ATTR(send, "idlocation"))) { - issues.push_back(InterpreterIssue("Syntax error in idlocation attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - if (HAS_ATTR(send, "delayexpr")) { - if (!_dataModel.isValidSyntax(ATTR(send, "delayexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in delayexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); - } - } - } - - } - - { - for (int i = 0; i < invokes.size(); i++) { - Element invoke = Element(invokes[i]); - if (HAS_ATTR(invoke, "typeexpr")) { - if (!_dataModel.isValidSyntax(ATTR(invoke, "typeexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in typeexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - if (HAS_ATTR(invoke, "srcexpr")) { - if (!_dataModel.isValidSyntax(ATTR(invoke, "srcexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in srcexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - if (HAS_ATTR(invoke, "idlocation")) { - if (!_dataModel.isValidSyntax(ATTR(invoke, "idlocation"))) { - issues.push_back(InterpreterIssue("Syntax error in idlocation attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - } - } - - { - for (int i = 0; i < cancels.size(); i++) { - Element cancel = Element(cancels[i]); - if (HAS_ATTR(cancel, "sendidexpr")) { - if (!_dataModel.isValidSyntax(ATTR(cancel, "sendidexpr"))) { - issues.push_back(InterpreterIssue("Syntax error in sendidexpr attribute", cancel, InterpreterIssue::USCXML_ISSUE_WARNING)); - continue; - } - } - } - } - - if (instantiatedDataModel) - _dataModel = DataModel(); - - return issues; + return InterpreterIssue::forInterpreter(this); } std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue) { @@ -2686,8 +2181,16 @@ Arabica::XPath::NodeSet InterpreterImpl::getInitialStates(Arabica:: std::cout << "Getting initial state of " << TAGNAME(state) << " " << ATTR(state, "id") << std::endl; #endif + if (isAtomic(state)) { + return Arabica::XPath::NodeSet(); + } + assert(isCompound(state) || isParallel(state)); + if (isParallel(state)) { + return getChildStates(state); + } + // initial attribute at element Arabica::DOM::Element stateElem = (Arabica::DOM::Element)state; if (stateElem.hasAttribute("initial")) { @@ -3003,8 +2506,8 @@ bool InterpreterImpl::isInitial(const Arabica::DOM::Element& state) if (isMember(state, getInitialStates(parent))) return true; // every nested node - if (isParallel(parent)) - return true; +// if (isParallel(parent)) +// return true; return false; } diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 984e861..b2ea027 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -39,6 +39,7 @@ #include "uscxml/concurrency/BlockingQueue.h" #include "uscxml/messages/Data.h" #include "uscxml/messages/SendRequest.h" +#include "uscxml/debug/InterpreterIssue.h" #include "uscxml/URL.h" #include "uscxml/plugins/DataModel.h" @@ -215,24 +216,6 @@ enum InterpreterState { }; USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterState& interpreterState); -class USCXML_API InterpreterIssue { -public: - enum IssueSeverity { - USCXML_ISSUE_FATAL, - USCXML_ISSUE_WARNING, - USCXML_ISSUE_INFO - }; - - InterpreterIssue(const std::string& msg, Arabica::DOM::Node node, IssueSeverity severity); - - std::string xPath; - std::string message; - Arabica::DOM::Node node; - IssueSeverity severity; -}; -USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue); - - class USCXML_API InterpreterImpl : public boost::enable_shared_from_this { public: @@ -571,6 +554,8 @@ protected: friend class USCXMLInvoker; friend class SCXMLIOProcessor; friend class Interpreter; + friend class InterpreterIssue; + }; class USCXML_API Interpreter { diff --git a/src/uscxml/debug/InterpreterIssue.cpp b/src/uscxml/debug/InterpreterIssue.cpp new file mode 100644 index 0000000..60f8f1b --- /dev/null +++ b/src/uscxml/debug/InterpreterIssue.cpp @@ -0,0 +1,656 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program 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. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include + +#include "InterpreterIssue.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" + +#include +#include + +namespace uscxml { + +using namespace Arabica::XPath; +using namespace Arabica::DOM; + +InterpreterIssue::InterpreterIssue(const std::string& msg, Arabica::DOM::Node node, IssueSeverity severity) : message(msg), node(node), severity(severity) { + if (node) + xPath = DOMUtils::xPathForNode(node); +} + +// find all elements in the SCXML namespace in one traversal +void assembleNodeSets(const std::string nsPrefix, const Node& node, std::map >& sets) { + NodeList childs = node.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + // std::cout << TAGNAME(childs.item(i)) << std::endl; + + if (TAGNAME_CAST(childs.item(i)).find(nsPrefix) == 0) { + // correct namespace, insert via localname + sets[LOCALNAME_CAST(childs.item(i))].push_back(childs.item(i)); + } + assembleNodeSets(nsPrefix, childs.item(i), sets); + } +} + +NodeSet getReachableStates(const Node& scxml, InterpreterImpl* interpreter, const std::string& nsPrefix) { + /** Check whether this state is reachable */ + + NodeSet reachable; + reachable.push_back(scxml); + + bool hasChanges = true; + + while (hasChanges) { + // iterate initials and transitions until stable + + hasChanges = false; + // reachable per initial attribute or document order - size will increase as we append new states + for (int i = 0; i < reachable.size(); i++) { + // get the state's initial states + Element state = Element(reachable[i]); + try { + NodeSet initials = interpreter->getInitialStates(state); + for (int j = 0; j < initials.size(); j++) { + Element initial = Element(initials[j]); + if (!InterpreterImpl::isMember(initial, reachable)) { + reachable.push_back(initial); + hasChanges = true; + } + } + } catch (Event e) { + } + } + + // reachable per target attribute in transitions + for (int i = 0; i < reachable.size(); i++) { + Element state = Element(reachable[i]); + NodeSet transitions = InterpreterImpl::filterChildElements(nsPrefix + "transition", state, false); + for (int j = 0; j < transitions.size(); j++) { + Element transition = Element(transitions[j]); + try { + NodeSet targets = interpreter->getTargetStates(transition); + for (int k = 0; k < targets.size(); k++) { + Element target = Element(targets[k]); + if (!InterpreterImpl::isMember(target, reachable)) { + reachable.push_back(target); + hasChanges = true; + } + } + } catch (Event e) { + } + } + } + } + + // reachable via a reachable child state + for (int i = 0; i < reachable.size(); i++) { + Element state = Element(reachable[i]); + if (InterpreterImpl::isAtomic(state)) { + // iterate the states parents + Node parent = state.getParentNode(); + while(parent && parent.getNodeType() == Node_base::ELEMENT_NODE) { + Element parentElem = Element(parent); + if (!InterpreterImpl::isState(parentElem)) { + break; + } + if (!InterpreterImpl::isMember(parentElem, reachable)) { + reachable.push_back(parent); + } + parent = parent.getParentNode(); + } + } + } + + return reachable; +} + + +std::list InterpreterIssue::forInterpreter(InterpreterImpl* interpreter) { + // some things we need to prepare first + if (interpreter->_factory == NULL) + interpreter->_factory = Factory::getInstance(); + interpreter->setupDOM(); + + std::list issues; + + if (!interpreter->_scxml) { + InterpreterIssue issue("No SCXML element to be found", Node(), InterpreterIssue::USCXML_ISSUE_FATAL); + issues.push_back(issue); + return issues; + } + + std::map > seenStates; + + // get some aliases + Element& _scxml = interpreter->_scxml; + NameSpaceInfo& _nsInfo = interpreter->_nsInfo; + Factory* _factory = interpreter->_factory; + DataModel& _dataModel = interpreter->_dataModel; + + + std::map > nodeSets; + assembleNodeSets(_nsInfo.xmlNSPrefix, _scxml, nodeSets); + + + NodeSet scxmls = nodeSets["scxml"]; + scxmls.push_back(_scxml); + + NodeSet reachable = getReachableStates(_scxml, interpreter, _nsInfo.xmlNSPrefix); + + NodeSet& states = nodeSets["state"]; + NodeSet& parallels = nodeSets["parallel"]; + NodeSet& transitions = nodeSets["transition"]; + NodeSet& initials = nodeSets["initial"]; + NodeSet& finals = nodeSets["final"]; + NodeSet& onEntries = nodeSets["onentry"]; + NodeSet& onExits = nodeSets["onexit"]; + NodeSet& histories = nodeSets["history"]; + + NodeSet& raises = nodeSets["raise"]; + NodeSet& ifs = nodeSets["if"]; + NodeSet& elseIfs = nodeSets["elseif"]; + NodeSet& elses = nodeSets["else"]; + NodeSet& foreachs = nodeSets["foreach"]; + NodeSet& logs = nodeSets["log"]; + + NodeSet& dataModels = nodeSets["datamodel"]; + NodeSet& datas = nodeSets["data"]; + NodeSet& assigns = nodeSets["assign"]; + NodeSet& doneDatas = nodeSets["donedata"]; + NodeSet& contents = nodeSets["content"]; + NodeSet& params = nodeSets["param"]; + NodeSet& scripts = nodeSets["script"]; + + NodeSet& sends = nodeSets["send"]; + NodeSet& cancels = nodeSets["cancel"]; + NodeSet& invokes = nodeSets["invoke"]; + NodeSet& finalizes = nodeSets["finalize"]; + + NodeSet allStates; + allStates.push_back(states); + allStates.push_back(parallels); + allStates.push_back(histories); + allStates.push_back(finals); + + NodeSet allExecContents; + allExecContents.push_back(raises); + allExecContents.push_back(ifs); + allExecContents.push_back(elseIfs); + allExecContents.push_back(elses); + allExecContents.push_back(foreachs); + allExecContents.push_back(logs); + allExecContents.push_back(sends); + allExecContents.push_back(assigns); + allExecContents.push_back(scripts); + allExecContents.push_back(cancels); + + NodeSet allElements; + allElements.push_back(allStates); + allElements.push_back(allExecContents); + allElements.push_back(transitions); + allElements.push_back(initials); + allElements.push_back(onEntries); + allElements.push_back(onExits); + allElements.push_back(dataModels); + allElements.push_back(datas); + allElements.push_back(doneDatas); + allElements.push_back(contents); + allElements.push_back(params); + allElements.push_back(invokes); + allElements.push_back(finalizes); + + + std::set execContentSet; + execContentSet.insert("raise"); + execContentSet.insert("if"); + execContentSet.insert("elseif"); + execContentSet.insert("else"); + execContentSet.insert("foreach"); + execContentSet.insert("log"); + execContentSet.insert("send"); + execContentSet.insert("assign"); + execContentSet.insert("script"); + execContentSet.insert("cancel"); + + // these are the valid children for elements in the SCXML namespace as per specification + std::map > validChildren; + validChildren["scxml"].insert("state"); + validChildren["scxml"].insert("parallel"); + validChildren["scxml"].insert("final"); + validChildren["scxml"].insert("datamodel"); + validChildren["scxml"].insert("script"); + + validChildren["state"].insert("onentry"); + validChildren["state"].insert("onexit"); + validChildren["state"].insert("transition"); + validChildren["state"].insert("initial"); + validChildren["state"].insert("state"); + validChildren["state"].insert("parallel"); + validChildren["state"].insert("final"); + validChildren["state"].insert("history"); + validChildren["state"].insert("datamodel"); + validChildren["state"].insert("invoke"); + + validChildren["parallel"].insert("onentry"); + validChildren["parallel"].insert("onexit"); + validChildren["parallel"].insert("transition"); + validChildren["parallel"].insert("state"); + validChildren["parallel"].insert("parallel"); + validChildren["parallel"].insert("history"); + validChildren["parallel"].insert("datamodel"); + validChildren["parallel"].insert("invoke"); + + validChildren["transition"] = execContentSet; + validChildren["onentry"] = execContentSet; + validChildren["onexit"] = execContentSet; + validChildren["finalize"] = execContentSet; + + validChildren["if"] = execContentSet; + validChildren["elseif"] = execContentSet; + validChildren["else"] = execContentSet; + validChildren["foreach"] = execContentSet; + + validChildren["initial"].insert("transition"); + validChildren["history"].insert("transition"); + + validChildren["final"].insert("onentry"); + validChildren["final"].insert("onexit"); + validChildren["final"].insert("donedata"); + + validChildren["datamodel"].insert("data"); + + validChildren["donedata"].insert("content"); + validChildren["donedata"].insert("param"); + + validChildren["send"].insert("content"); + validChildren["send"].insert("param"); + + validChildren["invoke"].insert("content"); + validChildren["invoke"].insert("param"); + validChildren["invoke"].insert("finalize"); + + // inverse validChildren to validParents + std::map > validParents; + for (std::map >::iterator parentIter = validChildren.begin(); parentIter != validChildren.end(); parentIter++) { + for (std::set::iterator childIter = parentIter->second.begin(); childIter != parentIter->second.end(); childIter++) { + validParents[*childIter].insert(parentIter->first); + } + } + + + for (int i = 0; i < allStates.size(); i++) { + Element state = Element(allStates[i]); + + if (InterpreterImpl::isMember(state, finals) && !HAS_ATTR(state, "id")) // id is not required for finals + continue; + + // check for existance of id attribute + if (!HAS_ATTR(state, "id")) { + issues.push_back(InterpreterIssue("State has no 'id' attribute", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + std::string stateId = ATTR(state, "id"); + + if (!InterpreterImpl::isMember(state, reachable)) { + issues.push_back(InterpreterIssue("State with id '" + stateId + "' is unreachable", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + + + + // check for uniqueness of id attribute + if (seenStates.find(stateId) != seenStates.end()) { + issues.push_back(InterpreterIssue("Duplicate state with id '" + stateId + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + seenStates[ATTR(state, "id")] = state; + } + + for (int i = 0; i < transitions.size(); i++) { + Element transition = Element(transitions[i]); + + // check for valid target + std::list targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target")); + for (std::list::iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { + if (seenStates.find(*targetIter) == seenStates.end()) { + issues.push_back(InterpreterIssue("Transition has non-existant target state with id '" + *targetIter + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + } + } + + // check for redundancy of transition + for (int i = 0; i < allStates.size(); i++) { + Element state = Element(allStates[i]); + NodeSet transitions = InterpreterImpl::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state, false); + + transitions.to_document_order(); + + for (int j = 1; j < transitions.size(); j++) { + Element transition = Element(transitions[j]); + for (int k = 0; k < j; k++) { + Element earlierTransition = Element(transitions[k]); + + // will the earlier transition always be enabled when the later is? + if (!HAS_ATTR(earlierTransition, "cond")) { + // earlier transition has no condition -> check event descriptor + if (!HAS_ATTR(earlierTransition, "event")) { + // earlier transition is eventless + issues.push_back(InterpreterIssue("Transition can never be optimally enabled", transition, InterpreterIssue::USCXML_ISSUE_INFO)); + goto NEXT_TRANSITION; + + } else if (HAS_ATTR(transition, "event")) { + // does the earlier transition match all our events? + std::list events = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "event")); + + bool allMatched = true; + for (std::list::iterator eventIter = events.begin(); eventIter != events.end(); eventIter++) { + if (!InterpreterImpl::nameMatch(ATTR(earlierTransition, "event"), *eventIter)) { + allMatched = false; + break; + } + } + + if (allMatched) { + issues.push_back(InterpreterIssue("Transition can never be optimally enabled", transition, InterpreterIssue::USCXML_ISSUE_INFO)); + goto NEXT_TRANSITION; + } + } + } + } + NEXT_TRANSITION:; + } + } + + // check for valid initial attribute + { + NodeSet withInitialAttr; + withInitialAttr.push_back(allStates); + withInitialAttr.push_back(_scxml); + + for (int i = 0; i < withInitialAttr.size(); i++) { + Element state = Element(withInitialAttr[i]); + if (HAS_ATTR(state, "initial")) { + std::list intials = InterpreterImpl::tokenizeIdRefs(ATTR(state, "initial")); + for (std::list::iterator initIter = intials.begin(); initIter != intials.end(); initIter++) { + if (seenStates.find(*initIter) == seenStates.end()) { + issues.push_back(InterpreterIssue("Initial attribute has invalid target state with id '" + *initIter + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + } + } + } + } + + // check that all invokers exists + { + for (int i = 0; i < invokes.size(); i++) { + Element invoke = Element(invokes[i]); + if (HAS_ATTR(invoke, "type") && !_factory->hasInvoker(ATTR(invoke, "type"))) { + issues.push_back(InterpreterIssue("Invoke with unknown type '" + ATTR(invoke, "type") + "'", invoke, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + } + } + + // check that all io processors exists + { + for (int i = 0; i < sends.size(); i++) { + Element send = Element(sends[i]); + if (HAS_ATTR(send, "type") && !_factory->hasIOProcessor(ATTR(send, "type"))) { + issues.push_back(InterpreterIssue("Send to unknown IO Processor '" + ATTR(send, "type") + "'", send, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + } + } + + // check that all custom executable content is known + { + NodeSet allExecContentContainers; + allExecContentContainers.push_back(onEntries); + allExecContentContainers.push_back(onExits); + allExecContentContainers.push_back(transitions); + allExecContentContainers.push_back(finalizes); + + for (int i = 0; i < allExecContentContainers.size(); i++) { + Element block = Element(allExecContentContainers[i]); + NodeSet execContents = InterpreterImpl::filterChildType(Node_base::ELEMENT_NODE, block); + for (int j = 0; j < execContents.size(); j++) { + Element execContent = Element(execContents[j]); + // SCXML specific executable content, always available + if (InterpreterImpl::isMember(execContent, allExecContents)) { + continue; + } + if (!_factory->hasExecutableContent(execContent.getLocalName(), execContent.getNamespaceURI())) { + issues.push_back(InterpreterIssue("Executable content element '" + execContent.getLocalName() + "' in namespace '" + execContent.getNamespaceURI() + "' unknown", execContent, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } + } + } + } + + // check that all SCXML elements have valid parents + { + for (int i = 0; i < allElements.size(); i++) { + Element element = Element(allElements[i]); + std::string localName = LOCALNAME(element); + if (!element.getParentNode() || element.getParentNode().getNodeType() != Node_base::ELEMENT_NODE) { + issues.push_back(InterpreterIssue("Parent of " + localName + " is no element", element, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + + Element parent = Element(element.getParentNode()); + std::string parentName = LOCALNAME(parent); + + if (validParents[localName].find(parentName) == validParents[localName].end()) { + issues.push_back(InterpreterIssue("Element " + localName + " can be no child of " + parentName, element, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + } + + + // check that the datamodel is known + if (HAS_ATTR(_scxml, "datamodel")) { + if (!_factory->hasDataModel(ATTR(_scxml, "datamodel"))) { + issues.push_back(InterpreterIssue("SCXML document requires unknown datamodel '" + ATTR(_scxml, "datamodel") + "'", _scxml, InterpreterIssue::USCXML_ISSUE_FATAL)); + + // we cannot even check the rest as we require a datamodel + return issues; + } + } + + bool instantiatedDataModel = false; + // instantiate datamodel if not explicitly set + if (!_dataModel) { + if (HAS_ATTR(_scxml, "datamodel")) { + // might throw + _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), interpreter); + instantiatedDataModel = true; + } else { + _dataModel = _factory->createDataModel("null", interpreter); + } + } + + + // test all scripts for valid syntax + { + for (int i = 0; i < scripts.size(); i++) { + Element script = Element(scripts[i]); + + if (script.hasChildNodes()) { + // search for the text node with the actual script + std::string scriptContent; + for (Node child = script.getFirstChild(); child; child = child.getNextSibling()) { + if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) + scriptContent += child.getNodeValue(); + } + + if (!_dataModel.isValidSyntax(scriptContent)) { + issues.push_back(InterpreterIssue("Syntax error in script", script, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + } + } + + // test the various attributes with datamodel expressions for valid syntax + { + NodeSet withCondAttrs; + withCondAttrs.push_back(transitions); + withCondAttrs.push_back(ifs); + withCondAttrs.push_back(elseIfs); + + for (int i = 0; i < withCondAttrs.size(); i++) { + Element condAttr = Element(withCondAttrs[i]); + if (HAS_ATTR(condAttr, "cond")) { + if (!_dataModel.isValidSyntax(ATTR(condAttr, "cond"))) { + issues.push_back(InterpreterIssue("Syntax error in cond attribute", condAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + } + } + + { + NodeSet withExprAttrs; + withExprAttrs.push_back(logs); + withExprAttrs.push_back(datas); + withExprAttrs.push_back(assigns); + withExprAttrs.push_back(contents); + withExprAttrs.push_back(params); + + for (int i = 0; i < withExprAttrs.size(); i++) { + Element withExprAttr = Element(withExprAttrs[i]); + if (HAS_ATTR(withExprAttr, "expr")) { + if (InterpreterImpl::isMember(withExprAttr, datas) || InterpreterImpl::isMember(withExprAttr, assigns)) { + if (!_dataModel.isValidSyntax("foo = " + ATTR(withExprAttr, "expr"))) { // TODO: this is ECMAScripty! + issues.push_back(InterpreterIssue("Syntax error in expr attribute", withExprAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } else { + if (!_dataModel.isValidSyntax(ATTR(withExprAttr, "expr"))) { + issues.push_back(InterpreterIssue("Syntax error in expr attribute", withExprAttr, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + } + } + } + + { + for (int i = 0; i < foreachs.size(); i++) { + Element foreach = Element(foreachs[i]); + if (HAS_ATTR(foreach, "array")) { + if (!_dataModel.isValidSyntax(ATTR(foreach, "array"))) { + issues.push_back(InterpreterIssue("Syntax error in array attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(foreach, "item")) { + if (!_dataModel.isValidSyntax(ATTR(foreach, "item"))) { + issues.push_back(InterpreterIssue("Syntax error in item attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(foreach, "index")) { + if (!_dataModel.isValidSyntax(ATTR(foreach, "index"))) { + issues.push_back(InterpreterIssue("Syntax error in index attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + } + } + + { + for (int i = 0; i < sends.size(); i++) { + Element send = Element(sends[i]); + if (HAS_ATTR(send, "eventexpr")) { + if (!_dataModel.isValidSyntax(ATTR(send, "eventexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in eventexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(send, "targetexpr")) { + if (!_dataModel.isValidSyntax(ATTR(send, "targetexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in targetexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(send, "typeexpr")) { + if (!_dataModel.isValidSyntax(ATTR(send, "typeexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in typeexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(send, "idlocation")) { + if (!_dataModel.isValidSyntax(ATTR(send, "idlocation"))) { + issues.push_back(InterpreterIssue("Syntax error in idlocation attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + if (HAS_ATTR(send, "delayexpr")) { + if (!_dataModel.isValidSyntax(ATTR(send, "delayexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in delayexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING)); + } + } + } + + } + + { + for (int i = 0; i < invokes.size(); i++) { + Element invoke = Element(invokes[i]); + if (HAS_ATTR(invoke, "typeexpr")) { + if (!_dataModel.isValidSyntax(ATTR(invoke, "typeexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in typeexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + if (HAS_ATTR(invoke, "srcexpr")) { + if (!_dataModel.isValidSyntax(ATTR(invoke, "srcexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in srcexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + if (HAS_ATTR(invoke, "idlocation")) { + if (!_dataModel.isValidSyntax(ATTR(invoke, "idlocation"))) { + issues.push_back(InterpreterIssue("Syntax error in idlocation attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + } + } + + { + for (int i = 0; i < cancels.size(); i++) { + Element cancel = Element(cancels[i]); + if (HAS_ATTR(cancel, "sendidexpr")) { + if (!_dataModel.isValidSyntax(ATTR(cancel, "sendidexpr"))) { + issues.push_back(InterpreterIssue("Syntax error in sendidexpr attribute", cancel, InterpreterIssue::USCXML_ISSUE_WARNING)); + continue; + } + } + } + } + + if (instantiatedDataModel) + _dataModel = DataModel(); + + return issues; +} + + +} \ No newline at end of file diff --git a/src/uscxml/debug/InterpreterIssue.h b/src/uscxml/debug/InterpreterIssue.h new file mode 100644 index 0000000..3949829 --- /dev/null +++ b/src/uscxml/debug/InterpreterIssue.h @@ -0,0 +1,54 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program 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. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef INTERPRETERISSUE_H_962CB305 +#define INTERPRETERISSUE_H_962CB305 + +#include "uscxml/Common.h" +#include +#include + +namespace uscxml { + +class InterpreterImpl; + +class USCXML_API InterpreterIssue { +public: + enum IssueSeverity { + USCXML_ISSUE_FATAL, + USCXML_ISSUE_WARNING, + USCXML_ISSUE_INFO + }; + + InterpreterIssue(const std::string& msg, Arabica::DOM::Node node, IssueSeverity severity); + + std::string xPath; + std::string message; + Arabica::DOM::Node node; + IssueSeverity severity; + +private: + static std::list forInterpreter(InterpreterImpl* interpreter); + friend class InterpreterImpl; +}; +USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue); + +} + +#endif /* end of include guard: INTERPRETERISSUE_H_962CB305 */ diff --git a/test/src/test-issue-reporting.cpp b/test/src/test-issue-reporting.cpp index 5de3366..3cdb141 100644 --- a/test/src/test-issue-reporting.cpp +++ b/test/src/test-issue-reporting.cpp @@ -27,6 +27,41 @@ int main(int argc, char** argv) { while(iterations--) { if (1) { + // Unreachable states + + const char* xml = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + std::set issueLocations = issueLocationsForXML(xml); + assert(issueLocations.find("//state[@id=\"bar\"]") != issueLocations.end()); + assert(issueLocations.size() == 1); + } + + if (1) { + // Invalid parents + + const char* xml = + "" + " " + " " + " " + ""; + + std::set issueLocations = issueLocationsForXML(xml); + assert(issueLocations.find("/scxml[1]/onentry[1]") != issueLocations.end()); + assert(issueLocations.size() == 1); + } + + if (1) { // State has no 'id' attribute const char* xml = "" @@ -310,6 +345,7 @@ int main(int argc, char** argv) { assert(issueLocations.size() == 1); } + } return EXIT_SUCCESS; -- cgit v0.12