From c34e0ce034586a05308e552cbbdff48beec7dd96 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Tue, 11 Mar 2014 15:45:38 +0100 Subject: Integrated debugger into browser (use -d command line parameter) --- CMakeLists.txt | 9 - README.md | 6 - apps/uscxml-browser.cpp | 19 +- apps/uscxml-debug.cpp | 81 - apps/uscxml-debugger.html | 2832 ++++++++++++++++++++ src/uscxml/DOMUtils.cpp | 23 +- src/uscxml/DOMUtils.h | 2 +- src/uscxml/Interpreter.cpp | 45 +- src/uscxml/Interpreter.h | 36 +- src/uscxml/Message.cpp | 7 +- src/uscxml/debug/Breakpoint.cpp | 65 +- src/uscxml/debug/Breakpoint.h | 29 +- src/uscxml/debug/DebugSession.cpp | 378 +++ src/uscxml/debug/DebugSession.h | 99 + src/uscxml/debug/Debugger.cpp | 114 +- src/uscxml/debug/Debugger.h | 60 +- src/uscxml/debug/DebuggerServlet.cpp | 330 +-- src/uscxml/debug/DebuggerServlet.h | 69 +- .../ioprocessor/basichttp/BasicHTTPIOProcessor.cpp | 2 +- src/uscxml/server/HTTPServer.cpp | 2 +- test/samples/uscxml/test-dirmon.scxml | 2 +- test/samples/w3c/draft/calc.scxml | 158 ++ 22 files changed, 3908 insertions(+), 460 deletions(-) delete mode 100644 apps/uscxml-debug.cpp create mode 100644 apps/uscxml-debugger.html create mode 100644 src/uscxml/debug/DebugSession.cpp create mode 100644 src/uscxml/debug/DebugSession.h create mode 100644 test/samples/w3c/draft/calc.scxml diff --git a/CMakeLists.txt b/CMakeLists.txt index f1ba3bd..7b2e89e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -937,15 +937,6 @@ if (NOT CMAKE_CROSSCOMPILING) set_target_properties(uscxml-browser PROPERTIES FOLDER "Apps") install_executable(TARGETS uscxml-browser COMPONENT tools) - add_executable(uscxml-debug apps/uscxml-debug.cpp) - target_link_libraries(uscxml-debug uscxml) - if (NOT CMAKE_CROSSCOMPILING) - set_target_properties(uscxml-debug PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) -# cotire(uscxml-browser) - endif() - set_target_properties(uscxml-debug PROPERTIES FOLDER "Apps") - install_executable(TARGETS uscxml-debug COMPONENT tools) - if (PROTOBUF_FOUND AND OFF) file(GLOB W3C-MMI-COMMON ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.cpp ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.h) diff --git a/README.md b/README.md index 002f4c0..9ec8b27 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,6 @@ uSCXML still fails the following ecmascript tests: - - - - - - diff --git a/apps/uscxml-browser.cpp b/apps/uscxml-browser.cpp index 4188132..81d8883 100644 --- a/apps/uscxml-browser.cpp +++ b/apps/uscxml-browser.cpp @@ -1,7 +1,7 @@ #include "uscxml/config.h" #include "uscxml/Interpreter.h" #include "uscxml/DOMUtils.h" -#include "uscxml/debug/SCXMLDotWriter.h" +#include "uscxml/debug/DebuggerServlet.h" #include #ifdef HAS_SIGNAL_H @@ -142,6 +142,12 @@ int main(int argc, char** argv) { } HTTPServer::getInstance(options.httpPort, options.wsPort, sslConf); + DebuggerServlet* debugger; + if (options.withDebugger) { + debugger = new DebuggerServlet(); + HTTPServer::getInstance()->registerServlet("/debug", debugger); + } + // instantiate and configure interpreters std::list interpreters; std::map::iterator confIter = options.interpreters.begin(); @@ -160,9 +166,8 @@ int main(int argc, char** argv) { VerboseMonitor* vm = new VerboseMonitor(); interpreter.addMonitor(vm); } - if (options.useDot) { - SCXMLDotWriter* dotWriter = new SCXMLDotWriter(); - interpreter.addMonitor(dotWriter); + if (options.withDebugger) { + interpreter.addMonitor(debugger); } interpreters.push_back(interpreter); @@ -194,5 +199,11 @@ int main(int argc, char** argv) { } } + if (options.withDebugger) { + // idle and wait for CTRL+C or debugging events + while(true) + tthread::this_thread::sleep_for(tthread::chrono::seconds(1)); + } + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/apps/uscxml-debug.cpp b/apps/uscxml-debug.cpp deleted file mode 100644 index e155ace..0000000 --- a/apps/uscxml-debug.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "uscxml/config.h" -#include "uscxml/Interpreter.h" -#include "uscxml/DOMUtils.h" -#include "uscxml/UUID.h" -#include "uscxml/debug/SCXMLDotWriter.h" -#include "uscxml/debug/Breakpoint.h" -#include "uscxml/debug/Debugger.h" -#include "uscxml/debug/DebuggerServlet.h" -#include -#include // mktime - -#include -#include - -#ifdef HAS_SIGNAL_H -#include -#endif - -#ifdef HAS_EXECINFO_H -#include -#endif - -#ifdef HAS_DLFCN_H -#include -#endif - -using namespace uscxml; - -//class Debugger : public HTTPServlet { -//public: -// -// std::string _url; -// Interpreter _interpreter; -// -// HTTPServer::Request _clientConn; // a request the client renews everytime -// concurrency::BlockingQueue _sendQueue; // queue of things we have to return to the client -// -// tthread::recursive_mutex _mutex; -// tthread::condition_variable _resumeCond; -// -// std::set _breakPoints; -// std::string _sessionId; -// -// DebuggerMonitor _monitor; -// -// virtual ~Debugger() { -// } -// -// // callbacks from http requests -// -// -// // helpers -// -// -// -// -//}; - - -int main(int argc, char** argv) { - using namespace uscxml; - - InterpreterOptions options = InterpreterOptions::fromCmdLine(argc, argv); - DebuggerServlet debuggerServlet; - - // setup logging - google::InitGoogleLogging(argv[0]); - google::AddLogSink(&debuggerServlet); - - // setup HTTP server - HTTPServer::getInstance(18088, 18089, NULL); - - - HTTPServer::getInstance()->registerServlet("/", &debuggerServlet); - - while(true) - tthread::this_thread::sleep_for(tthread::chrono::seconds(1)); - - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/apps/uscxml-debugger.html b/apps/uscxml-debugger.html new file mode 100644 index 0000000..daf63e0 --- /dev/null +++ b/apps/uscxml-debugger.html @@ -0,0 +1,2832 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + diff --git a/src/uscxml/DOMUtils.cpp b/src/uscxml/DOMUtils.cpp index c38b087..3cf986b 100644 --- a/src/uscxml/DOMUtils.cpp +++ b/src/uscxml/DOMUtils.cpp @@ -25,9 +25,14 @@ namespace uscxml { -std::string DOMUtils::xPathForNode(const Arabica::DOM::Node& node) { +std::string DOMUtils::xPathForNode(const Arabica::DOM::Node& node, const std::string& ns) { std::string xPath; - + std::string nsPrefix; + + if (ns.size() > 0) { + nsPrefix = ns + ":"; + } + if (!node || node.getNodeType() != Arabica::DOM::Node_base::ELEMENT_NODE) return xPath; @@ -37,12 +42,16 @@ std::string DOMUtils::xPathForNode(const Arabica::DOM::Node& node) case Arabica::DOM::Node_base::ELEMENT_NODE: { if (HAS_ATTR(curr, "id")) { // we assume ids to be unique and return immediately - xPath.insert(0, "//" + TAGNAME(curr) + "[@id=\"" + ATTR(curr, "id") + "\"]"); + if (ns == "*") { + xPath.insert(0, "//*[local-name() = \"" + TAGNAME(curr) + "\"][@id=\"" + ATTR(curr, "id") + "\"]"); + } else { + xPath.insert(0, "//" + nsPrefix + TAGNAME(curr) + "[@id=\"" + ATTR(curr, "id") + "\"]"); + } return xPath; } else { // check previous siblings to count our index Arabica::DOM::Node sibling = curr.getPreviousSibling(); - int index = 1; + int index = 1; // xpath indices start at 1 while(sibling) { if (sibling.getNodeType() == Arabica::DOM::Node_base::ELEMENT_NODE) { if (iequals(TAGNAME(sibling), TAGNAME(curr))) { @@ -51,7 +60,11 @@ std::string DOMUtils::xPathForNode(const Arabica::DOM::Node& node) } sibling = sibling.getPreviousSibling(); } - xPath.insert(0, "/" + TAGNAME(curr) + "[" + toStr(index) + "]"); + if (ns == "*") { + xPath.insert(0, "/*[local-name() = \"" + TAGNAME(curr) + "\"][" + toStr(index) + "]"); + } else { + xPath.insert(0, "/" + nsPrefix + TAGNAME(curr) + "[" + toStr(index) + "]"); + } } break; } diff --git a/src/uscxml/DOMUtils.h b/src/uscxml/DOMUtils.h index 731b332..596bb36 100644 --- a/src/uscxml/DOMUtils.h +++ b/src/uscxml/DOMUtils.h @@ -35,7 +35,7 @@ namespace uscxml { class USCXML_API DOMUtils { public: - static std::string xPathForNode(const Arabica::DOM::Node& node); + static std::string xPathForNode(const Arabica::DOM::Node& node, const std::string& ns = ""); static std::list > getElementsByType(const Arabica::DOM::Node& root, Arabica::DOM::Node_base::Type type); }; diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 2e867ba..bfee23a 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -98,7 +98,7 @@ void InterpreterOptions::printUsageAndExit(const char* progName) { printf("\n"); printf("Options\n"); printf("\t-v : be verbose\n"); - printf("\t-d : write each configuration as a dot file\n"); + printf("\t-d : enable debugging via HTTP\n"); printf("\t-lN : Set loglevel to N\n"); printf("\t-tN : port for HTTP server\n"); printf("\t-sN : port for HTTPS server\n"); @@ -120,7 +120,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { optind = 0; struct option longOptions[] = { {"verbose", no_argument, 0, 'v'}, - {"dot", no_argument, 0, 'd'}, + {"debug", no_argument, 0, 'd'}, {"port", required_argument, 0, 't'}, {"ssl-port", required_argument, 0, 's'}, {"ws-port", required_argument, 0, 'w'}, @@ -134,11 +134,6 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { }; opterr = 0; - if (argc < 2) { - options.error = "No SCXML document to evaluate"; - return options; - } - InterpreterOptions* currOptions = &options; // parse global options @@ -183,7 +178,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { currOptions->pluginPath = optarg; break; case 'd': - currOptions->useDot = true; + currOptions->withDebugger = true; break; case 'c': currOptions->certificate = optarg; @@ -228,7 +223,7 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { DONE_PARSING_CMD: - if (options.interpreters.size() == 0) + if (options.interpreters.size() == 0 && !options.withDebugger) options.error = "No SCXML document to evaluate"; return options; @@ -328,6 +323,7 @@ Interpreter Interpreter::fromURI(const std::string& uri) { // try to establish URI root for relative src attributes in document if (interpreter) { interpreter._impl->_baseURI = URL::asBaseURL(absUrl); + interpreter._impl->_sourceURI = absUrl; } else { LOG(ERROR) << "Cannot create interpreter from URI '" << absUrl.asString() << "'"; } @@ -1310,16 +1306,26 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont if (content.getNodeType() != Node_base::ELEMENT_NODE) return; - if (false) { - } else if (iequals(TAGNAME(content), _xmlNSPrefix + "onentry") || - iequals(TAGNAME(content), _xmlNSPrefix + "onexit") || - iequals(TAGNAME(content), _xmlNSPrefix + "finalize") || - iequals(TAGNAME(content), _xmlNSPrefix + "transition")) { + if (iequals(TAGNAME(content), _xmlNSPrefix + "onentry") || + iequals(TAGNAME(content), _xmlNSPrefix + "onexit") || + iequals(TAGNAME(content), _xmlNSPrefix + "finalize") || + iequals(TAGNAME(content), _xmlNSPrefix + "transition")) { // --- CONVENIENCE LOOP -------------------------- NodeList executable = content.getChildNodes(); for (int i = 0; i < executable.getLength(); i++) { executeContent(executable.item(i), rethrow); } + return; + } + + // --- MONITOR: beforeExecutingContent ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->beforeExecutingContent(shared_from_this(), content); + } USCXML_MONITOR_CATCH_BLOCK(beforeExecutingContent) + } + + if (false) { } else if (iequals(TAGNAME(content), _xmlNSPrefix + "raise")) { // --- RAISE -------------------------- if (HAS_ATTR(content, "event")) { @@ -1453,6 +1459,9 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont } } catch (Event exception) { // script failed to download + if (exception.name == "error.communication") { + throw exception; // terminate test329 + } receive(exception); return; } @@ -1526,6 +1535,14 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont } execContent.exitElement(content); } + + // --- MONITOR: afterExecutingContent ------------------------------ + for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { + try { + (*monIter)->afterExecutingContent(shared_from_this(), content); + } USCXML_MONITOR_CATCH_BLOCK(afterExecutingContent) + } + } void InterpreterImpl::returnDoneEvent(const Arabica::DOM::Node& state) { diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 94c5d74..f6b1783 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -98,7 +98,7 @@ enum Capabilities { class USCXML_API InterpreterOptions { public: - bool useDot; + bool withDebugger; bool verbose; bool withHTTP; bool withHTTPS; @@ -126,7 +126,7 @@ public: protected: InterpreterOptions() : - useDot(false), + withDebugger(false), verbose(false), withHTTP(true), withHTTPS(true), @@ -171,12 +171,19 @@ public: _monitors.erase(monitor); } - void setBaseURI(std::string baseURI) { - _baseURI = URL(baseURI); + void setSourceURI(std::string sourceURI) { + _sourceURI = URL(sourceURI); + + URL baseURI(sourceURI); + URL::toBaseURL(baseURI); + _baseURI = baseURI; } URL getBaseURI() { return _baseURI; } + URL getSourceURI() { + return _sourceURI; + } void setCmdLineOptions(std::map params); Data getCmdLineOptions() { @@ -213,6 +220,11 @@ public: return _nameSpaceInfo[ns] + ":"; return ""; } + + Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { + return _xpath.evaluate(xpathExpr, _scxml).asNodeSet(); + } + void setNameSpaceInfo(const std::map nameSpaceInfo); std::map getNameSpaceInfo() { return _nameSpaceInfo; @@ -339,6 +351,7 @@ protected: tthread::recursive_mutex _pluginMutex; URL _baseURI; + URL _sourceURI; Arabica::DOM::Document _document; Arabica::DOM::Element _scxml; Arabica::XPath::XPath _xpath; @@ -478,8 +491,11 @@ public: return _impl->removeMonitor(monitor); } - void setBaseURI(std::string baseURI) { - return _impl->setBaseURI(baseURI); + void setSourceURI(std::string sourceURI) { + return _impl->setSourceURI(sourceURI); + } + URL getSourceURI() { + return _impl->getSourceURI(); } URL getBaseURI() { return _impl->getBaseURI(); @@ -527,7 +543,10 @@ public: std::string getXMLPrefixForNS(const std::string& ns) { return _impl->getXMLPrefixForNS(ns); } - + Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { + return _impl->getNodeSetForXPath(xpathExpr); + } + void inline receiveInternal(const Event& event) { return _impl->receiveInternal(event); } @@ -709,6 +728,9 @@ public: virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state) {} virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element& state) {} + virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content) {} + virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content) {} + virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp index aeb0027..33dca84 100644 --- a/src/uscxml/Message.cpp +++ b/src/uscxml/Message.cpp @@ -430,6 +430,7 @@ Data Data::fromJSON(const std::string& jsonString) { std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); if (dataStack.back()->type == Data::VERBATIM) { boost::replace_all(value, "\\\"", "\""); + boost::replace_all(value, "\\n", "\n"); } dataStack.back()->atom = value; dataStack.pop_back(); @@ -728,10 +729,11 @@ std::string Data::toJSON(const Data& data) { // escape string if (false) { } else if (data.atom[i] == '"') { - os << '\\'; - os << data.atom[i]; + os << "\\\""; } else if (data.atom[i] == '\n') { os << "\\n"; + } else if (data.atom[i] == '\t') { + os << "\\t"; } else { os << data.atom[i]; } @@ -746,6 +748,7 @@ std::string Data::toJSON(const Data& data) { std::string xmlSer = xmlSerSS.str(); boost::replace_all(xmlSer, "\"", "\\\""); boost::replace_all(xmlSer, "\n", "\\n"); + boost::replace_all(xmlSer, "\t", "\\t"); os << "\"" << xmlSer << "\""; } else { if (data.type == Data::VERBATIM) { diff --git a/src/uscxml/debug/Breakpoint.cpp b/src/uscxml/debug/Breakpoint.cpp index f64efad..58cf295 100644 --- a/src/uscxml/debug/Breakpoint.cpp +++ b/src/uscxml/debug/Breakpoint.cpp @@ -19,10 +19,12 @@ #include "uscxml/debug/Breakpoint.h" #include "uscxml/Interpreter.h" +#include "uscxml/DOMUtils.h" namespace uscxml { Breakpoint::Breakpoint(const Data& data) { + enabled = true; subject = UNDEF_SUBJECT; when = UNDEF_WHEN; action = UNDEF_ACTION; @@ -65,7 +67,7 @@ Breakpoint::Breakpoint(const Data& data) { subject = EVENT; } else if (data["subject"].atom == "invoker") { subject = INVOKER; - } else if (data["subject"].atom == "exec") { + } else if (data["subject"].atom == "executable") { subject = EXECUTABLE; } } @@ -82,6 +84,12 @@ Breakpoint::Breakpoint(const Data& data) { if (data.hasKey("eventName")) eventName = data["eventName"].atom; + if (data.hasKey("executableName")) + executableName = data["executableName"].atom; + + if (data.hasKey("executableXPath")) + executableXPath = data["executableXPath"].atom; + if (data.hasKey("stateId")) stateId = data["stateId"].atom; @@ -116,7 +124,7 @@ Data Breakpoint::toData() const { data.compound["subject"] = Data("invoker", Data::VERBATIM); break; case EXECUTABLE: - data.compound["subject"] = Data("exec", Data::VERBATIM); + data.compound["subject"] = Data("executable", Data::VERBATIM); break; default: break; @@ -162,6 +170,16 @@ Data Breakpoint::toData() const { if (eventName.length() > 0) data.compound["eventName"] = Data(eventName, Data::VERBATIM); + if (executableName.length() > 0) + data.compound["executableName"] = Data(executableName, Data::VERBATIM); + + if (executableXPath.length() > 0) { + data.compound["executableXPath"] = Data(executableXPath, Data::VERBATIM); + } + + if (element) + data.compound["xpath"] = Data(DOMUtils::xPathForNode(element, "*"), Data::VERBATIM); + if (stateId.length() > 0) data.compound["stateId"] = Data(stateId, Data::VERBATIM); @@ -177,34 +195,31 @@ Data Breakpoint::toData() const { return data; } -bool Breakpoint::matches(const Breakpoint& other) const { +bool Breakpoint::matches(Interpreter interpreter, const Breakpoint& other) const { // would we match the given breakpoint? if (subject != UNDEF_SUBJECT && - other.subject != UNDEF_SUBJECT && other.subject != subject) return false; // subject does not match if (when != UNDEF_WHEN && - other.when != UNDEF_WHEN && other.when != when) return false; // time does not match if (action != UNDEF_ACTION && - other.action != UNDEF_ACTION && other.action != action) return false; // action does not match // when we have a qualifier it has to match - if(invokeId.length() > 0 && !InterpreterImpl::nameMatch(invokeId, other.invokeId)) { + if(invokeId.length() > 0 && invokeId != other.invokeId) { return false; } - if(invokeType.length() > 0 && !InterpreterImpl::nameMatch(invokeType, other.invokeType)) { + if(invokeType.length() > 0 && invokeType != other.invokeType) { return false; } - if(stateId.length() > 0 && !InterpreterImpl::nameMatch(stateId, other.stateId)) { + if(stateId.length() > 0 && stateId != other.stateId) { return false; } @@ -212,19 +227,39 @@ bool Breakpoint::matches(const Breakpoint& other) const { return false; } - if(transSource.length() > 0 && !InterpreterImpl::nameMatch(transSource, other.transSource)) { + if(executableName.length() > 0 && executableName != other.executableName) { return false; } - if(transTarget.length() > 0 && !InterpreterImpl::nameMatch(transTarget, other.transTarget)) { + if(executableXPath.length()) { + Arabica::XPath::NodeSet nodes; + try { + nodes = interpreter.getNodeSetForXPath(executableXPath); + } catch (...) { + return false; + } + return Interpreter::isMember(other.element, nodes); + } + + if(transSource.length() > 0 && transSource != other.transSource) { return false; } - - return true; -} + if(transTarget.length() > 0 && transTarget != other.transTarget) { + return false; + } -bool Breakpoint::isValid() { + if (condition.length() > 0) { + try { + DataModel dm = interpreter.getDataModel(); + if (!dm || !dm.evalAsBool(condition)) { + return false; + } + } catch (...) { + return false; + } + } + return true; } diff --git a/src/uscxml/debug/Breakpoint.h b/src/uscxml/debug/Breakpoint.h index b2861d8..cd6fea5 100644 --- a/src/uscxml/debug/Breakpoint.h +++ b/src/uscxml/debug/Breakpoint.h @@ -21,6 +21,7 @@ #define BREAKPOINT_H_VR7K7T1X #include "uscxml/Message.h" +#include "uscxml/Interpreter.h" namespace uscxml { @@ -39,35 +40,49 @@ public: UNDEF_ACTION, ENTER, EXIT, INVOKE, UNINVOKE }; - Breakpoint() {} + Breakpoint() { + subject = UNDEF_SUBJECT; + when = UNDEF_WHEN; + action = UNDEF_ACTION; + } Breakpoint(const Data& data); // would we match the given breakpoint as well? - bool matches(const Breakpoint& other) const; - - bool isValid(); - + bool matches(Interpreter interpreter, const Breakpoint& other) const; + Data toData() const; bool operator<(const Breakpoint& other) const { - return (origData < other.origData); + return (toData() < other.toData()); } + operator bool() { + return (subject != UNDEF_SUBJECT || + when != UNDEF_WHEN || + action != UNDEF_ACTION); + } + + mutable bool enabled; + When when; Subject subject; Action action; + Arabica::DOM::Element element; + std::string invokeId; std::string invokeType; std::string eventName; + std::string executableName; + std::string executableXPath; + std::string stateId; std::string transSource; std::string transTarget; std::string condition; - Data origData; }; } diff --git a/src/uscxml/debug/DebugSession.cpp b/src/uscxml/debug/DebugSession.cpp new file mode 100644 index 0000000..46b414f --- /dev/null +++ b/src/uscxml/debug/DebugSession.cpp @@ -0,0 +1,378 @@ +/** + * @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 "uscxml/debug/DebugSession.h" +#include "uscxml/debug/Debugger.h" + +namespace uscxml { + +void DebugSession::checkBreakpoints(const std::list qualifiedBreakpoints) { + std::list::const_iterator qualifiedBreakpointIter = qualifiedBreakpoints.begin(); + + if (!_breakpointsEnabled) + return; + + while(qualifiedBreakpointIter != qualifiedBreakpoints.end()) { + const Breakpoint& qualifiedBreakpoint = *qualifiedBreakpointIter++; + + // check if one of the user-supplied breakpoints match + bool userBreakpointMatched = false; + Data replyData; + + if (_skipTo) { + if (_skipTo.matches(_interpreter, qualifiedBreakpoint)) { + replyData.compound["breakpoint"] = _skipTo.toData(); + replyData.compound["qualified"] = qualifiedBreakpoint.toData(); + breakExecution(replyData); + _skipTo = Breakpoint(); + } + continue; + } + + std::set::const_iterator breakpointIter = _breakPoints.begin(); + while(breakpointIter != _breakPoints.end()) { + const Breakpoint& breakpoint = *breakpointIter++; + if (!breakpoint.enabled) + continue; + if (breakpoint.matches(_interpreter, qualifiedBreakpoint)) { + // do we have a condition? + + replyData.compound["breakpoint"] = breakpoint.toData(); + replyData.compound["qualified"] = qualifiedBreakpoint.toData(); + + userBreakpointMatched = true; + breakExecution(replyData); + } + } + if (_isStepping && !userBreakpointMatched) { + replyData.compound["qualified"] = qualifiedBreakpoint.toData(); + breakExecution(replyData); + + } + } +} + +void DebugSession::breakExecution(Data replyData) { + tthread::lock_guard lock(_mutex); + + Arabica::XPath::NodeSet basicConf = _interpreter.getBasicConfiguration(); + for (int i = 0; i < basicConf.size(); i++) { + Arabica::DOM::Element element = Arabica::DOM::Element(basicConf[i]); + if (element.hasAttribute("id")) { + replyData.compound["basicStates"].array.push_back(Data(element.getAttribute("id"), Data::VERBATIM)); + } + } + + Arabica::XPath::NodeSet activeConf = _interpreter.getConfiguration(); + for (int i = 0; i < activeConf.size(); i++) { + Arabica::DOM::Element element = Arabica::DOM::Element(activeConf[i]); + if (element.hasAttribute("id")) { + replyData.compound["activeStates"].array.push_back(Data(element.getAttribute("id"), Data::VERBATIM)); + } + } + + replyData.compound["replyType"] = Data("breakpoint", Data::VERBATIM); + _debugger->pushData(shared_from_this(), replyData); + _resumeCond.wait(_mutex); +} + +Data DebugSession::debugPrepare(const Data& data) { + Data replyData; + + if (!data.hasKey("xml") && !data.hasKey("url")) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No XML or URL given", Data::VERBATIM); + return replyData; + } + + debugStop(data); + + _isAttached = false; + + if (data.hasKey("xml")) { + _interpreter = Interpreter::fromXML(data["xml"].atom); + } else if (data.hasKey("url")) { + _interpreter = Interpreter::fromURI(data["url"].atom); + } else { + _interpreter = Interpreter(); + } + + if (_interpreter) { + // register ourself as a monitor + _interpreter.addMonitor(_debugger); + _debugger->attachSession(_interpreter, shared_from_this()); + if (data.hasKey("url")) { + // this allows to resolve relative external reources + _interpreter.setSourceURI(data["url"].atom); + } + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + + return replyData; +} + +Data DebugSession::debugAttach(const Data& data) { + Data replyData; + _isAttached = true; + + if (!data.hasKey("attach")) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No id to attach to given", Data::VERBATIM); + return replyData; + } + + std::string interpreterId = data["attach"].atom; + bool interpreterFound = false; + + // find interpreter for sessionid + std::map > instances = Interpreter::getInstances(); + for (std::map >::iterator instIter = instances.begin(); + instIter != instances.end(); + instIter++) { + + boost::shared_ptr instance = instIter->second.lock(); + if (instance && instance->getSessionId() == interpreterId) { + _interpreter = instance; + _debugger->attachSession(_interpreter, shared_from_this()); + interpreterFound = true; + break; + } + } + + if (!interpreterFound) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No interpreter with given id found", Data::VERBATIM); + } else { + replyData.compound["xml"].node = _interpreter.getDocument(); + replyData.compound["status"] = Data("success", Data::VERBATIM); + } + + return replyData; +} + +Data DebugSession::debugDetach(const Data& data) { + Data replyData; + _isAttached = false; + + _debugger->detachSession(_interpreter); + replyData.compound["status"] = Data("success", Data::VERBATIM); + return replyData; +} + +Data DebugSession::debugStart(const Data& data) { + Data replyData; + + if (_isAttached) { + replyData.compound["reason"] = Data("Already started when attached", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } else if (!_interpreter) { + replyData.compound["reason"] = Data("No interpreter attached or loaded", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } else { + _interpreter.start(); + replyData.compound["status"] = Data("success", Data::VERBATIM); + } + + return replyData; +} + +Data DebugSession::debugStop(const Data& data) { + Data replyData; + + if (_interpreter) { + // detach from old intepreter + _debugger->detachSession(_interpreter); + } + + if (_interpreter && !_isAttached) + _interpreter.stop(); + // unblock + _resumeCond.notify_all(); + + _skipTo = Breakpoint(); + replyData.compound["status"] = Data("success", Data::VERBATIM); + + // calls destructor + _interpreter = Interpreter(); + + return replyData; +} + +Data DebugSession::debugStep(const Data& data) { + tthread::lock_guard lock(_mutex); + + stepping(true); + _resumeCond.notify_one(); + + Data replyData; + if (_interpreter) { + // register ourself as a monitor + if (!_interpreter.isRunning()) + _interpreter.start(); + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + return replyData; +} + +Data DebugSession::debugResume(const Data& data) { + tthread::lock_guard lock(_mutex); + + stepping(false); + + Data replyData; + replyData.compound["status"] = Data("success", Data::VERBATIM); + + _resumeCond.notify_one(); + return replyData; +} + + +Data DebugSession::debugPause(const Data& data) { + tthread::lock_guard lock(_mutex); + + _skipTo = Breakpoint(); + stepping(true); + + Data replyData; + replyData.compound["status"] = Data("success", Data::VERBATIM); + + return replyData; +} + +Data DebugSession::skipToBreakPoint(const Data& data) { + tthread::lock_guard lock(_mutex); + + _skipTo = Breakpoint(data); + + Data replyData; + replyData.compound["status"] = Data("success", Data::VERBATIM); + + _resumeCond.notify_one(); + return replyData; +} + +Data DebugSession::addBreakPoint(const Data& data) { + Breakpoint breakpoint(data); + + Data replyData; + if (_breakPoints.find(breakpoint) == _breakPoints.end()) { + _breakPoints.insert(breakpoint); + replyData.compound["status"] = Data("success", Data::VERBATIM); + + } else { + replyData.compound["reason"] = Data("Breakpoint already exists", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + return replyData; +} + +Data DebugSession::removeBreakPoint(const Data& data) { + Breakpoint breakpoint(data); + + Data replyData; + if (_breakPoints.find(breakpoint) != _breakPoints.end()) { + _breakPoints.erase(breakpoint); + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + return replyData; +} + +Data DebugSession::enableBreakPoint(const Data& data) { + Breakpoint breakpoint(data); + + Data replyData; + if (_breakPoints.find(breakpoint) != _breakPoints.end()) { + _breakPoints.find(breakpoint)->enabled = true; + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + + return replyData; +} +Data DebugSession::disableBreakPoint(const Data& data) { + Breakpoint breakpoint(data); + + Data replyData; + if (_breakPoints.find(breakpoint) != _breakPoints.end()) { + _breakPoints.find(breakpoint)->enabled = false; + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["reason"] = Data("No such breakpoint", Data::VERBATIM); + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + + return replyData; +} +Data DebugSession::enableAllBreakPoints() { + Data replyData; + + _breakpointsEnabled = true; + replyData.compound["status"] = Data("success", Data::VERBATIM); + + return replyData; +} +Data DebugSession::disableAllBreakPoints() { + Data replyData; + + _breakpointsEnabled = false; + replyData.compound["status"] = Data("success", Data::VERBATIM); + + return replyData; +} + +Data DebugSession::debugEval(const Data& data) { + Data replyData; + + if (!data.hasKey("expression")) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No expression given", Data::VERBATIM); + return replyData; + } + + std::string expr = data["expression"].atom; + + if (!_interpreter) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No interpreter running", Data::VERBATIM); + } else if (!_interpreter.getDataModel()) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No datamodel available", Data::VERBATIM); + } else { + try { + replyData.compound["eval"] = _interpreter.getDataModel().getStringAsData(expr); + } catch (Event e) { + replyData.compound["eval"] = e.data; + replyData.compound["eval"].compound["error"] = Data(e.name, Data::VERBATIM); + } + replyData.compound["status"] = Data("success", Data::VERBATIM); + } + return replyData; +} + + +} \ No newline at end of file diff --git a/src/uscxml/debug/DebugSession.h b/src/uscxml/debug/DebugSession.h new file mode 100644 index 0000000..12f1d93 --- /dev/null +++ b/src/uscxml/debug/DebugSession.h @@ -0,0 +1,99 @@ +/** + * @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 DEBUGSESSION_H_M8YHEGV6 +#define DEBUGSESSION_H_M8YHEGV6 + +#include "uscxml/debug/Breakpoint.h" +#include "uscxml/Interpreter.h" +#include + +namespace uscxml { + +class Debugger; + +class USCXML_API DebugSession : public boost::enable_shared_from_this { +public: + DebugSession() { + _isStepping = false; + _isAttached = false; + _breakpointsEnabled = true; + _markedForDeletion = false; + _debugger = NULL; + } + + void stepping(bool enable) { + _isStepping = enable; + } + + void checkBreakpoints(const std::list qualifiedBreakpoints); + + Data debugPrepare(const Data& data); + Data debugAttach(const Data& data); + Data debugDetach(const Data& data); + Data debugStart(const Data& data); + Data debugStop(const Data& data); + Data debugStep(const Data& data); + Data debugResume(const Data& data); + Data debugPause(const Data& data); + Data skipToBreakPoint(const Data& data); + Data addBreakPoint(const Data& data); + Data removeBreakPoint(const Data& data); + Data enableBreakPoint(const Data& data); + Data disableBreakPoint(const Data& data); + Data enableAllBreakPoints(); + Data disableAllBreakPoints(); + Data debugEval(const Data& data); + + void setDebugger(Debugger* debugger) { + _debugger = debugger; + } + + Interpreter getInterpreter() { + return _interpreter; + } + + void markForDeletion(bool mark) { + _markedForDeletion = mark; + } + +protected: + void breakExecution(Data replyData); + + bool _isStepping; + bool _isAttached; + bool _breakpointsEnabled; + + tthread::condition_variable _resumeCond; + tthread::recursive_mutex _runMutex; + tthread::recursive_mutex _mutex; + + bool _markedForDeletion; + Debugger* _debugger; + Interpreter _interpreter; + std::set _breakPoints; + Breakpoint _skipTo; + +}; + + +} + + +#endif /* end of include guard: DEBUGSESSION_H_M8YHEGV6 */ diff --git a/src/uscxml/debug/Debugger.cpp b/src/uscxml/debug/Debugger.cpp index aa97a22..3a19228 100644 --- a/src/uscxml/debug/Debugger.cpp +++ b/src/uscxml/debug/Debugger.cpp @@ -23,9 +23,13 @@ namespace uscxml { void Debugger::afterCompletion(Interpreter interpreter) { + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; + Data msg; msg.compound["replyType"] = Data("finished", Data::VERBATIM); - pushData(msg); + pushData(session, msg); } std::list getQualifiedStateBreakpoints(Interpreter interpreter, const Arabica::DOM::Element& state, Breakpoint breakpointTemplate) { @@ -33,6 +37,7 @@ std::list getQualifiedStateBreakpoints(Interpreter interpreter, cons Breakpoint bp = breakpointTemplate; // copy base as template bp.stateId = ATTR(state, "id"); + bp.element = state; bp.subject = Breakpoint::STATE; breakpoints.push_back(bp); @@ -44,6 +49,7 @@ std::list getQualifiedInvokeBreakpoints(Interpreter interpreter, con Breakpoint bp = breakpointTemplate; // copy base as template bp.subject = Breakpoint::INVOKER; + bp.element = invokeElem; bp.invokeId = invokeId; if (HAS_ATTR(invokeElem, "type")) { @@ -67,6 +73,7 @@ std::list getQualifiedTransBreakpoints(Interpreter interpreter, cons Arabica::DOM::Element target(targets[j]); Breakpoint bp = breakpointTemplate; // copy base as template + bp.element = transition; bp.transSource = ATTR(source, "id"); bp.transTarget = ATTR(target, "id"); bp.subject = Breakpoint::TRANSITION; @@ -83,6 +90,12 @@ void Debugger::beforeTakingTransition(Interpreter interpreter, const Arabica::DO void Debugger::afterTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition) { handleTransition(interpreter, transition, Breakpoint::AFTER); } +void Debugger::beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content) { + handleExecutable(interpreter, Arabica::DOM::Element(content), Breakpoint::BEFORE); +} +void Debugger::afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content) { + handleExecutable(interpreter, Arabica::DOM::Element(content), Breakpoint::AFTER); +} void Debugger::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state) { handleState(interpreter, state, Breakpoint::BEFORE, Breakpoint::EXIT); } @@ -120,9 +133,34 @@ void Debugger::beforeProcessingEvent(Interpreter interpreter, const Event& event handleEvent(interpreter, event, Breakpoint::BEFORE); } +void Debugger::handleExecutable(Interpreter interpreter, + const Arabica::DOM::Element& execContentElem, + Breakpoint::When when) { + if (!interpreter.isRunning()) + return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; + + std::list breakpoints; + + Breakpoint breakpoint; + breakpoint.when = when; + breakpoint.element = execContentElem; + breakpoint.executableName = execContentElem.getLocalName(); + breakpoint.subject = Breakpoint::EXECUTABLE; + breakpoints.push_back(breakpoint); + + session->checkBreakpoints(breakpoints); + +} + void Debugger::handleEvent(Interpreter interpreter, const Event& event, Breakpoint::When when) { if (!interpreter.isRunning()) return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; std::list breakpoints; @@ -132,13 +170,16 @@ void Debugger::handleEvent(Interpreter interpreter, const Event& event, Breakpoi breakpoint.subject = Breakpoint::EVENT; breakpoints.push_back(breakpoint); - checkBreakpoints(interpreter, breakpoints); + session->checkBreakpoints(breakpoints); } void Debugger::handleStable(Interpreter interpreter, Breakpoint::When when) { if (!interpreter.isRunning()) return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; std::list breakpoints; @@ -147,13 +188,16 @@ void Debugger::handleStable(Interpreter interpreter, Breakpoint::When when) { breakpoint.subject = Breakpoint::STABLE; breakpoints.push_back(breakpoint); - checkBreakpoints(interpreter, breakpoints); + session->checkBreakpoints(breakpoints); } void Debugger::handleMicrostep(Interpreter interpreter, Breakpoint::When when) { if (!interpreter.isRunning()) return; - + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; + std::list breakpoints; Breakpoint breakpoint; @@ -161,86 +205,50 @@ void Debugger::handleMicrostep(Interpreter interpreter, Breakpoint::When when) { breakpoint.subject = Breakpoint::MICROSTEP; breakpoints.push_back(breakpoint); - checkBreakpoints(interpreter, breakpoints); + session->checkBreakpoints(breakpoints); } void Debugger::handleTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, Breakpoint::When when) { if (!interpreter.isRunning()) return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; Breakpoint breakpointTemplate; breakpointTemplate.when = when; std::list qualifiedBreakpoints = getQualifiedTransBreakpoints(interpreter, transition, breakpointTemplate); - checkBreakpoints(interpreter, qualifiedBreakpoints); + session->checkBreakpoints(qualifiedBreakpoints); } void Debugger::handleState(Interpreter interpreter, const Arabica::DOM::Element& state, Breakpoint::When when, Breakpoint::Action action) { if (!interpreter.isRunning()) return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; Breakpoint breakpointTemplate; breakpointTemplate.when = when; breakpointTemplate.action = action; std::list qualifiedBreakpoints = getQualifiedStateBreakpoints(interpreter, state, breakpointTemplate); - checkBreakpoints(interpreter, qualifiedBreakpoints); + session->checkBreakpoints(qualifiedBreakpoints); } void Debugger::handleInvoke(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeId, Breakpoint::When when, Breakpoint::Action action) { if (!interpreter.isRunning()) return; + boost::shared_ptr session = getSession(interpreter); + if (!session) + return; Breakpoint breakpointTemplate; breakpointTemplate.when = when; breakpointTemplate.action = action; std::list qualifiedBreakpoints = getQualifiedInvokeBreakpoints(interpreter, invokeElem, invokeId, breakpointTemplate); - checkBreakpoints(interpreter, qualifiedBreakpoints); - -} - -void Debugger::checkBreakpoints(Interpreter interpreter, const std::list qualifiedBreakpoints) { - std::list::const_iterator qualifiedBreakpointIter = qualifiedBreakpoints.begin(); - while(qualifiedBreakpointIter != qualifiedBreakpoints.end()) { - const Breakpoint& qualifiedBreakpoint = *qualifiedBreakpointIter++; - - // check if one of the user-supplied breakpoints match - bool userBreakpointMatched = false; - std::set::const_iterator breakpointIter = _breakPoints.begin(); - while(breakpointIter != _breakPoints.end()) { - const Breakpoint& breakpoint = *breakpointIter++; - if (breakpoint.matches(qualifiedBreakpoint)) { - Data replyData; - replyData.compound["breakpoint"] = breakpoint.toData(); - replyData.compound["qualified"] = qualifiedBreakpoint.toData(); - - userBreakpointMatched = true; - hitBreakpoint(interpreter, replyData); - } - } - if (_isStepping && !userBreakpointMatched) { - Data replyData; - replyData.compound["qualified"] = qualifiedBreakpoint.toData(); - hitBreakpoint(interpreter, replyData); - } - } -} + session->checkBreakpoints(qualifiedBreakpoints); -void Debugger::send(google::LogSeverity severity, const char* full_filename, - const char* base_filename, int line, - const struct ::tm* tm_time, - const char* message, size_t message_len) { - - // _sendQueue is thread-safe, not sure about ToString though - - LogMessage msg(severity, - full_filename, - base_filename, - line, - tm_time, - std::string(message, message_len), - ToString(severity, base_filename, line, tm_time, message, message_len)); - msg.compound["replyType"] = Data("log", Data::VERBATIM); - pushData(msg); } diff --git a/src/uscxml/debug/Debugger.h b/src/uscxml/debug/Debugger.h index dfc197d..c49e90f 100644 --- a/src/uscxml/debug/Debugger.h +++ b/src/uscxml/debug/Debugger.h @@ -23,45 +23,40 @@ #include "uscxml/Message.h" #include "uscxml/Interpreter.h" #include "uscxml/debug/Breakpoint.h" - -#include +#include "uscxml/debug/DebugSession.h" namespace uscxml { -class USCXML_API Debugger : public InterpreterMonitor, public google::LogSink { +class USCXML_API Debugger : public InterpreterMonitor { public: Debugger() { - _isStepping = false; } virtual ~Debugger() {} - - class LogMessage : public Data { - public: - LogMessage(google::LogSeverity severity, const char* full_filename, - const char* base_filename, int line, - const struct ::tm* tm_time, - std::string message, std::string formatted) { - - compound["severity"] = severity; - compound["fullFilename"] = Data(full_filename, Data::VERBATIM); - compound["baseFilename"] = Data(base_filename, Data::VERBATIM); - compound["line"] = line; - compound["message"] = Data(message, Data::VERBATIM); - compound["time"] = Data(mktime((struct ::tm*)tm_time), Data::INTERPRETED); - compound["formatted"] = Data(formatted, Data::VERBATIM); - } - }; - virtual void pushData(Data pushData) = 0; - virtual void hitBreakpoint(const Interpreter& interpreter, Data data) = 0; + virtual void attachSession(Interpreter interpreter, boost::shared_ptr session) { + tthread::lock_guard lock(_sessionMutex); + _sessionForInterpreter[interpreter] = session; + } + + virtual void detachSession(Interpreter interpreter) { + tthread::lock_guard lock(_sessionMutex); + _sessionForInterpreter.erase(interpreter); + } - void stepping(bool enable) { - _isStepping = enable; + virtual boost::shared_ptr getSession(Interpreter interpreter) { + tthread::lock_guard lock(_sessionMutex); + if (_sessionForInterpreter.find(interpreter) != _sessionForInterpreter.end()) + return _sessionForInterpreter[interpreter]; + return boost::shared_ptr(); } + virtual void pushData(boost::shared_ptr session, Data pushData) = 0; + // InterpreterMonitor virtual void beforeProcessingEvent(Interpreter interpreter, const Event& event); virtual void beforeMicroStep(Interpreter interpreter); + virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content); + virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Node& content); virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state); virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element& state); virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid); @@ -79,12 +74,6 @@ public: virtual void beforeCompletion(Interpreter interpreter) {} virtual void afterCompletion(Interpreter interpreter); - // Logsink - virtual void send(google::LogSeverity severity, const char* full_filename, - const char* base_filename, int line, - const struct ::tm* tm_time, - const char* message, size_t message_len); - protected: void handleTransition(Interpreter interpreter, @@ -99,14 +88,15 @@ protected: const std::string& invokeId, Breakpoint::When when, Breakpoint::Action action); + void handleExecutable(Interpreter interpreter, + const Arabica::DOM::Element& execContentElem, + Breakpoint::When when); void handleStable(Interpreter interpreter, Breakpoint::When when); void handleMicrostep(Interpreter interpreter, Breakpoint::When when); void handleEvent(Interpreter interpreter, const Event& event, Breakpoint::When when); - void checkBreakpoints(Interpreter interpreter, const std::list qualifiedBreakpoints); - bool _isStepping; - std::set _breakPoints; - + tthread::recursive_mutex _sessionMutex; + std::map > _sessionForInterpreter; }; } diff --git a/src/uscxml/debug/DebuggerServlet.cpp b/src/uscxml/debug/DebuggerServlet.cpp index e179b8c..55ced75 100644 --- a/src/uscxml/debug/DebuggerServlet.cpp +++ b/src/uscxml/debug/DebuggerServlet.cpp @@ -23,25 +23,30 @@ namespace uscxml { -void DebuggerServlet::pushData(Data pushData) { +void DebuggerServlet::pushData(boost::shared_ptr session, Data pushData) { std::cout << "trying to push " << pushData["replyType"].atom << std::endl; - _sendQueue.push(pushData); - serverPushData(); + + if (!session) { + if (_sendQueues.size() > 0) // logging is not aware of its interpreter + _sendQueues.begin()->second.push(pushData); + } else { + _sendQueues[session].push(pushData); + } + + serverPushData(session); } -void DebuggerServlet::serverPushData() { - tthread::lock_guard lock(_mutex); - - if (_sendQueue.isEmpty()) +void DebuggerServlet::serverPushData(boost::shared_ptr session) { + if (_sendQueues[session].isEmpty()) return; - if (!_clientConn) + if (!_clientConns[session]) return; - Data reply = _sendQueue.pop(); + Data reply = _sendQueues[session].pop(); std::cout << "pushing " << reply["replyType"].atom << std::endl; - returnData(_clientConn, reply); - _clientConn = HTTPServer::Request(); + returnData(_clientConns[session], reply); + _clientConns[session] = HTTPServer::Request(); } void DebuggerServlet::returnData(const HTTPServer::Request& request, Data replyData) { @@ -56,17 +61,6 @@ void DebuggerServlet::returnData(const HTTPServer::Request& request, Data replyD reply.headers["Content-Type"] = "application/json"; HTTPServer::reply(reply); } - -void DebuggerServlet::hitBreakpoint(const Interpreter& interpreter, - Data data) { - tthread::lock_guard lock(_mutex); - - data.compound["replyType"] = Data("breakpoint", Data::VERBATIM); - pushData(data); - - _resumeCond.wait(_mutex); - tthread::this_thread::sleep_for(tthread::chrono::milliseconds(200)); -} bool DebuggerServlet::isCORS(const HTTPServer::Request& request) { return (request.data["type"].atom == "options" && @@ -101,220 +95,164 @@ bool DebuggerServlet::httpRecvRequest(const HTTPServer::Request& request) { std::cout << request.data["path"] << ": " << request.data["content"] << std::endl; + Data replyData; + // process request that don't need a session if (false) { - } else if (boost::starts_with(request.data["path"].atom, "/poll")) { - processPoll(request); - } else if (boost::starts_with(request.data["path"].atom, "/connect")) { + } else if (boost::starts_with(request.data["path"].atom, "/debug/connect")) { processConnect(request); - } else if (boost::starts_with(request.data["path"].atom, "/disconnect")) { - processDisconnect(request); - } else if (boost::starts_with(request.data["path"].atom, "/sessions")) { + return true; + } else if (boost::starts_with(request.data["path"].atom, "/debug/sessions")) { processListSessions(request); - } else if (boost::starts_with(request.data["path"].atom, "/breakpoint/add")) { - processAddBreakPoint(request); - } else if (boost::starts_with(request.data["path"].atom, "/breakpoint/remove")) { - processRemoveBreakPoint(request); + return true; + } + + // get session or return error + if (false) { + } else if (!request.data["content"].hasKey("session")) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No session given", Data::VERBATIM); + } else if (_sessionForId.find(request.data["content"]["session"].atom) == _sessionForId.end()) { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No such session", Data::VERBATIM); + } + if (replyData) { + returnData(request, replyData); + return true; + } + + boost::shared_ptr session = _sessionForId[request.data["content"]["session"].atom]; + + if (false) { + } else if (boost::starts_with(request.data["path"].atom, "/debug/poll")) { + // save long-standing client poll + _clientConns[session] = request; + serverPushData(session); + + } else if (boost::starts_with(request.data["path"].atom, "/debug/disconnect")) { + processDisconnect(request); + + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/enable/all")) { + replyData = session->enableAllBreakPoints(); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/disable/all")) { + replyData = session->disableAllBreakPoints(); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/skipto")) { + replyData = session->skipToBreakPoint(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/add")) { + replyData = session->addBreakPoint(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/remove")) { + replyData = session->removeBreakPoint(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/enable")) { + replyData = session->enableBreakPoint(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/breakpoint/disable")) { + replyData = session->disableBreakPoint(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/stop")) { + replyData = session->debugStop(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/prepare")) { - processDebugPrepare(request); + replyData = session->debugPrepare(request.data["content"]); + } else if (boost::starts_with(request.data["path"].atom, "/debug/attach")) { + replyData = session->debugAttach(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/start")) { - processDebugStart(request); - } else if (boost::starts_with(request.data["path"].atom, "/debug/stop")) { - processDebugStop(request); + replyData = session->debugStart(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/step")) { - processDebugStep(request); + replyData = session->debugStep(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/pause")) { - processDebugPause(request); + replyData = session->debugPause(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/resume")) { - processDebugResume(request); + replyData = session->debugResume(request.data["content"]); } else if (boost::starts_with(request.data["path"].atom, "/debug/eval")) { - processDebugEval(request); + replyData = session->debugEval(request.data["content"]); } - return true; -} - -void DebuggerServlet::processPoll(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); - _clientConn = request; - serverPushData(); -} - -void DebuggerServlet::processDebugPrepare(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); - -// std::cout << "clearing all pushes" << std::endl; -// _sendQueue.clear(); - - // this will call the destructor if _interpreter already is set - _resumeCond.notify_all(); - _interpreter = Interpreter::fromXML(request.data["content"].atom); - - Data replyData; - if (_interpreter) { - // register ourself as a monitor - _interpreter.addMonitor(this); - replyData.compound["status"] = Data("success", Data::VERBATIM); - } else { - replyData.compound["status"] = Data("failure", Data::VERBATIM); + if (replyData) { + returnData(request, replyData); + return true; } - returnData(request, replyData); + + return true; } -void DebuggerServlet::processDebugStart(const HTTPServer::Request& request) { +// someone connected, create a new session +void DebuggerServlet::processConnect(const HTTPServer::Request& request) { tthread::lock_guard lock(_mutex); + std::string sessionId = UUID::getUUID(); + + _sessionForId[sessionId] = boost::shared_ptr(new DebugSession()); + _sessionForId[sessionId]->setDebugger(this); Data replyData; - if (_interpreter) { - // register ourself as a monitor - _interpreter.start(); - replyData.compound["status"] = Data("success", Data::VERBATIM); - } else { - replyData.compound["status"] = Data("failure", Data::VERBATIM); - } + replyData.compound["session"] = Data(sessionId, Data::VERBATIM); + replyData.compound["status"] = Data("success", Data::VERBATIM); returnData(request, replyData); } -void DebuggerServlet::processDebugStop(const HTTPServer::Request& request) { -// tthread::lock_guard lock(_mutex); - - stepping(false); +void DebuggerServlet::processDisconnect(const HTTPServer::Request& request) { + tthread::lock_guard lock(_mutex); Data replyData; - if (_interpreter) { - _interpreter.stop(); - _resumeCond.notify_all(); // unblock breakpoints - _interpreter.join(); - _interpreter = Interpreter(); // empty interpreter, calls destructor - replyData.compound["status"] = Data("success", Data::VERBATIM); - } else { - replyData.compound["status"] = Data("failure", Data::VERBATIM); - replyData.compound["reason"] = Data("Interpreter already stopped", Data::VERBATIM); - } - returnData(request, replyData); -} -void DebuggerServlet::processDebugEval(const HTTPServer::Request& request) { - Data replyData; - if (!_interpreter) { - replyData.compound["status"] = Data("failure", Data::VERBATIM); - replyData.compound["reason"] = Data("No interpreter running", Data::VERBATIM); - } else if (!_interpreter.getDataModel()) { + if (!request.data["content"].hasKey("session")) { replyData.compound["status"] = Data("failure", Data::VERBATIM); - replyData.compound["reason"] = Data("No datamodel available", Data::VERBATIM); - } else if (!request.data["content"].hasKey("expression")) { - replyData.compound["status"] = Data("failure", Data::VERBATIM); - replyData.compound["reason"] = Data("No expression given", Data::VERBATIM); - } else { - std::string expr = request.data["content"]["expression"].atom; - try { - replyData.compound["eval"] = _interpreter.getDataModel().getStringAsData(expr); - } catch (Event e) { - replyData.compound["eval"] = e.data; - replyData.compound["eval"].compound["error"] = Data(e.name, Data::VERBATIM); - } - replyData.compound["status"] = Data("success", Data::VERBATIM); + replyData.compound["reason"] = Data("No session given", Data::VERBATIM); + returnData(request, replyData); } - returnData(request, replyData); -} -void DebuggerServlet::processDebugStep(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); + std::string sessionId = request.data["content"]["session"].atom; - stepping(true); - _resumeCond.notify_one(); - - Data replyData; - if (_interpreter && !_interpreter.isRunning()) { - // register ourself as a monitor - _interpreter.start(); - replyData.compound["status"] = Data("success", Data::VERBATIM); - } else { + if (_sessionForId.find(sessionId) == _sessionForId.end()) { replyData.compound["status"] = Data("failure", Data::VERBATIM); + replyData.compound["reason"] = Data("No such session", Data::VERBATIM); + } else { + replyData.compound["status"] = Data("success", Data::VERBATIM); + detachSession(_sessionForId[sessionId]->getInterpreter()); + _sessionForId[sessionId]->debugStop(request.data["content"]); + _clientConns.erase(_sessionForId[sessionId]); + _sendQueues.erase(_sessionForId[sessionId]); + _sessionForId.erase(sessionId); } - returnData(request, replyData); - -} - -void DebuggerServlet::processDebugResume(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); - - stepping(false); - - Data replyData; - replyData.compound["status"] = Data("success", Data::VERBATIM); - returnData(request, replyData); - - _resumeCond.notify_one(); -} - -void DebuggerServlet::processDebugPause(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); - - Data replyData; - replyData.compound["status"] = Data("success", Data::VERBATIM); - returnData(request, replyData); -} - -void DebuggerServlet::processConnect(const HTTPServer::Request& request) { - tthread::lock_guard lock(_mutex); - _sessionId = UUID::getUUID(); - _breakPoints.clear(); -// _sendQueue.clear(); - Data replyData; - replyData.compound["session"] = Data(_sessionId, Data::VERBATIM); - replyData.compound["status"] = Data("success", Data::VERBATIM); returnData(request, replyData); } void DebuggerServlet::processListSessions(const HTTPServer::Request& request) { Data replyData; - - // TODO: return actual data - Data sessionData; - sessionData.compound["name"] = Data("Not actually a Session", Data::VERBATIM); - sessionData.compound["id"] = Data("23452523-wg23g2g2-234t2g-23g2g", Data::VERBATIM); - replyData.compound["sessions"].array.push_back(sessionData); - - sessionData.compound["name"] = Data("But returned from the server!", Data::VERBATIM); - sessionData.compound["id"] = Data("swfgsgfw-g232vqvq-234t2g-23g2g", Data::VERBATIM); - replyData.compound["sessions"].array.push_back(sessionData); - - replyData.compound["status"] = Data("success", Data::VERBATIM); - returnData(request, replyData); -} - -void DebuggerServlet::processDisconnect(const HTTPServer::Request& request) { - Data replyData; - replyData.compound["status"] = Data("success", Data::VERBATIM); - returnData(request, replyData); -} - -void DebuggerServlet::processAddBreakPoint(const HTTPServer::Request& request) { - Breakpoint breakPoint(request.data["content"]); - Data replyData; - if (breakPoint.isValid()) { - replyData.compound["status"] = Data("success", Data::VERBATIM); - - if (_breakPoints.find(breakPoint) == _breakPoints.end()) { - _breakPoints.insert(breakPoint); + + std::map > instances = Interpreter::getInstances(); + for (std::map >::iterator instIter = instances.begin(); + instIter != instances.end(); + instIter++) { + + boost::shared_ptr instance = instIter->second.lock(); + if (instance) { + Data sessionData; + sessionData.compound["name"] = Data(instance->getName(), Data::VERBATIM); + sessionData.compound["id"] = Data(instance->getSessionId(), Data::VERBATIM); + sessionData.compound["source"] = Data(instance->getSourceURI().asString(), Data::VERBATIM); + sessionData.compound["xml"].node = instance->getDocument(); + + replyData.compound["sessions"].array.push_back(sessionData); } - } else { - replyData.compound["status"] = Data("failure", Data::VERBATIM); } + + replyData.compound["status"] = Data("success", Data::VERBATIM); returnData(request, replyData); } -void DebuggerServlet::processRemoveBreakPoint(const HTTPServer::Request& request) { - Breakpoint breakPoint(request.data["content"]); - Data replyData; - if (_breakPoints.erase(breakPoint) > 0) { - replyData.compound["status"] = Data("success", Data::VERBATIM); - } else { - replyData.compound["message"] = Data("No such breakpoint", Data::VERBATIM); - replyData.compound["status"] = Data("failure", Data::VERBATIM); - } - returnData(request, replyData); +void DebuggerServlet::send(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct ::tm* tm_time, + const char* message, size_t message_len) { + + // _sendQueue is thread-safe, not sure about ToString though + + LogMessage msg(severity, + full_filename, + base_filename, + line, + tm_time, + std::string(message, message_len), + ToString(severity, base_filename, line, tm_time, message, message_len)); + msg.compound["replyType"] = Data("log", Data::VERBATIM); + pushData(boost::shared_ptr(), msg); } diff --git a/src/uscxml/debug/DebuggerServlet.h b/src/uscxml/debug/DebuggerServlet.h index 5cd0be9..ae5178f 100644 --- a/src/uscxml/debug/DebuggerServlet.h +++ b/src/uscxml/debug/DebuggerServlet.h @@ -22,6 +22,7 @@ #include "uscxml/Common.h" #include "getopt.h" +#include #include "uscxml/server/HTTPServer.h" #include "uscxml/Interpreter.h" @@ -31,8 +32,25 @@ namespace uscxml { -class USCXML_API DebuggerServlet : public Debugger, public HTTPServlet { +class USCXML_API DebuggerServlet : public Debugger, public HTTPServlet, public google::LogSink { public: + class LogMessage : public Data { + public: + LogMessage(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct ::tm* tm_time, + std::string message, std::string formatted) { + + compound["severity"] = severity; + compound["fullFilename"] = Data(full_filename, Data::VERBATIM); + compound["baseFilename"] = Data(base_filename, Data::VERBATIM); + compound["line"] = line; + compound["message"] = Data(message, Data::VERBATIM); + compound["time"] = Data(mktime((struct ::tm*)tm_time), Data::INTERPRETED); + compound["formatted"] = Data(formatted, Data::VERBATIM); + } + }; + virtual ~DebuggerServlet() {} // from Debugger @@ -46,36 +64,43 @@ public: _url = url; } - void pushData(Data pushData); + void pushData(boost::shared_ptr session, Data pushData); void returnData(const HTTPServer::Request& request, Data replyData); - void hitBreakpoint(const Interpreter& interpreter, - Data data); - - void processDebugEval(const HTTPServer::Request& request); - void processDebugPrepare(const HTTPServer::Request& request); - void processDebugStart(const HTTPServer::Request& request); - void processDebugStop(const HTTPServer::Request& request); - void processDebugStep(const HTTPServer::Request& request); - void processDebugResume(const HTTPServer::Request& request); - void processDebugPause(const HTTPServer::Request& request); + void processDisconnect(const HTTPServer::Request& request); void processConnect(const HTTPServer::Request& request); void processListSessions(const HTTPServer::Request& request); - void processDisconnect(const HTTPServer::Request& request); - void processAddBreakPoint(const HTTPServer::Request& request); - void processRemoveBreakPoint(const HTTPServer::Request& request); - void processPoll(const HTTPServer::Request& request); + +// void processDebugPrepare(const HTTPServer::Request& request); +// void processDebugAttach(const HTTPServer::Request& request); +// void processDebugStart(const HTTPServer::Request& request); +// void processDebugStop(const HTTPServer::Request& request); + +// void processDebugEval(const HTTPServer::Request& request); +// void processDebugStart(const HTTPServer::Request& request); +// void processDebugStop(const HTTPServer::Request& request); +// void processDebugStep(const HTTPServer::Request& request); +// void processDebugResume(const HTTPServer::Request& request); +// void processDebugPause(const HTTPServer::Request& request); +// void processAddBreakPoint(const HTTPServer::Request& request); +// void processRemoveBreakPoint(const HTTPServer::Request& request); +// void processPoll(const HTTPServer::Request& request); + // Logsink + virtual void send(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct ::tm* tm_time, + const char* message, size_t message_len); + protected: - void serverPushData(); + void serverPushData(boost::shared_ptr); - Interpreter _interpreter; - std::string _sessionId; std::string _url; - HTTPServer::Request _clientConn; - tthread::condition_variable _resumeCond; + std::map, HTTPServer::Request> _clientConns; + std::map, concurrency::BlockingQueue > _sendQueues; + std::map > _sessionForId; + tthread::recursive_mutex _mutex; - concurrency::BlockingQueue _sendQueue; }; } diff --git a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp index 51e1e28..085d919 100644 --- a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp +++ b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp @@ -263,7 +263,7 @@ void BasicHTTPIOProcessor::downloadCompleted(const URL& url) { std::map >::iterator reqIter = _sendRequests.begin(); while(reqIter != _sendRequests.end()) { if (reqIter->second.first == url) { - // test 513 + // test513 std::string statusCode = url.getStatusCode(); if (statusCode.length() > 0) { std::string statusPrefix = statusCode.substr(0,1); diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp index 6c26811..69096fc 100644 --- a/src/uscxml/server/HTTPServer.cpp +++ b/src/uscxml/server/HTTPServer.cpp @@ -105,7 +105,7 @@ HTTPServer::HTTPServer(unsigned short port, unsigned short wsPort, SSLConfig* ss LOG(ERROR) << "HTTP server cannot bind to tcp/" << _port; } } - + _wsPort = wsPort; if (_wsPort > 0) { _wsHandle = evws_bind_socket(_evws, _wsPort); diff --git a/test/samples/uscxml/test-dirmon.scxml b/test/samples/uscxml/test-dirmon.scxml index 872757f..1fba300 100644 --- a/test/samples/uscxml/test-dirmon.scxml +++ b/test/samples/uscxml/test-dirmon.scxml @@ -2,7 +2,7 @@
Test#StatusDescriptionComment
301Failed"the processor should reject this document because it can't download the script"uSCXML continues processing as if there was no <script> element.
329 Failed "test that none of the system variables can be modified"