diff options
-rw-r--r-- | src/qml/jsruntime/qv4vme_moth.cpp | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index 6f1838728e..1491d1644f 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -33,8 +33,14 @@ #include "qv4vme_moth_p.h" #include "qv4instr_moth_p.h" + +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> + #include <private/qv4value_inl_p.h> #include <private/qv4debugging_p.h> +#include <private/qv4function_p.h> +#include <private/qv4functionobject_p.h> #include <private/qv4math_p.h> #include <private/qv4scopedvalue_p.h> #include <private/qv4lookup_p.h> @@ -52,6 +58,228 @@ # define TRACE(n, str, ...) #endif // DO_TRACE_INSTR +extern "C" { + +// This is the interface to Qt Creator's (new) QML debugger. + +/*! \internal + \since 5.5 + + This function is called uncondionally from VME::run(). + + An attached debugger can set a breakpoint here to + intercept calls to VME::run(). + */ + +Q_QML_EXPORT void qt_v4ResolvePendingBreakpointsHook() +{ +} + +/*! \internal + \since 5.5 + + This function is called when a QML interpreter breakpoint + is hit. + + An attached debugger can set a breakpoint here. +*/ +Q_QML_EXPORT void qt_v4TriggeredBreakpointHook() +{ +} + +/*! \internal + \since 5.5 + + The main entry point into "Native Mixed" Debugging. + + Commands are passed as UTF-8 encoded JSON data. + The data has two compulsory fields: + \list + \li \c version: Version of the protocol (currently 1) + \li \c command: Name of the command + \endlist + + Depending on \c command, more fields can be present. + + Error is indicated by negative return values, + success by non-negative return values. + + \c protocolVersion: + Returns version of implemented protocol. + + \c insertBreakpoint: + Sets a breakpoint on a given file and line. + \list + \li \c fullName: Name of the QML/JS file + \li \c lineNumber: Line number in the file + \li \c condition: Breakpoint condition + \endlist + Returns a unique positive number as handle. + + \c removeBreakpoint: + Removes a breakpoint from a given file and line. + \list + \li \c fullName: Name of the QML/JS file + \li \c lineNumber: Line number in the file + \li \c condition: Breakpoint condition + \endlist + Returns zero on success, a negative number on failure. + + \c prepareStep: + Puts the interpreter in stepping mode. + Returns zero. + +*/ +Q_QML_EXPORT int qt_v4DebuggerHook(const char *json); + + +} // extern "C" + +static int qt_v4BreakpointCount = 0; +static bool qt_v4IsDebugging = true; +static bool qt_v4IsStepping = false; + +class Breakpoint +{ +public: + Breakpoint() : bpNumber(0), lineNumber(-1) {} + + bool matches(const QString &file, int line) const + { + return fullName == file && lineNumber == line; + } + + int bpNumber; + int lineNumber; + QString fullName; // e.g. /opt/project/main.qml + QString engineName; // e.g. qrc:/main.qml + QString condition; // optional +}; + +static QVector<Breakpoint> qt_v4Breakpoints; +static Breakpoint qt_v4LastStop; + +static QV4::Function *qt_v4ExtractFunction(QV4::ExecutionContext *context) +{ + QV4::Scope scope(context->engine()); + QV4::ScopedFunctionObject function(scope, context->getFunctionObject()); + if (function) + return function->function(); + else + return context->d()->engine->globalCode; +} + +static void qt_v4TriggerBreakpoint(const Breakpoint &bp, QV4::Function *function) +{ + qt_v4LastStop = bp; + + // Set up some auxiliary data for informational purpose. + // This is not part of the protocol. + QV4::Heap::String *functionName = function->name(); + QByteArray functionNameUtf8; + if (functionName) + functionNameUtf8 = functionName->toQString().toUtf8(); + + qt_v4TriggeredBreakpointHook(); // Trigger Breakpoint. +} + +int qt_v4DebuggerHook(const char *json) +{ + const int ProtocolVersion = 1; + + enum { + Success = 0, + WrongProtocol, + NoSuchCommand, + NoSuchBreakpoint + }; + + QJsonDocument doc = QJsonDocument::fromJson(json); + QJsonObject ob = doc.object(); + QByteArray command = ob.value(QStringLiteral("command")).toString().toUtf8(); + + if (command == "protocolVersion") { + return ProtocolVersion; // Version number. + } + + int version = ob.value(QLatin1Literal("version")).toString().toInt(); + if (version != ProtocolVersion) { + return -WrongProtocol; + } + + if (command == "insertBreakpoint") { + Breakpoint bp; + bp.bpNumber = ++qt_v4BreakpointCount; + bp.lineNumber = ob.value(QStringLiteral("lineNumber")).toString().toInt(); + bp.engineName = ob.value(QStringLiteral("engineName")).toString(); + bp.fullName = ob.value(QStringLiteral("fullName")).toString(); + bp.condition = ob.value(QStringLiteral("condition")).toString(); + qt_v4Breakpoints.append(bp); + return bp.bpNumber; + } + + if (command == "removeBreakpoint") { + int lineNumber = ob.value(QStringLiteral("lineNumber")).toString().toInt(); + QString fullName = ob.value(QStringLiteral("fullName")).toString(); + if (qt_v4Breakpoints.last().matches(fullName, lineNumber)) { + qt_v4Breakpoints.removeLast(); + return Success; + } + for (int i = 0; i + 1 < qt_v4Breakpoints.size(); ++i) { + if (qt_v4Breakpoints.at(i).matches(fullName, lineNumber)) { + qt_v4Breakpoints[i] = qt_v4Breakpoints.takeLast(); + return Success; // Ok. + } + } + return -NoSuchBreakpoint; // Failure + } + + if (command == "prepareStep") { + qt_v4IsStepping = true; + return Success; // Ok. + } + + + return -NoSuchCommand; // Failure. +} + +static void qt_v4CheckForBreak(QV4::ExecutionContext *context, QV4::Value **scopes, int scopeDepth) +{ + Q_UNUSED(scopes); + Q_UNUSED(scopeDepth); + const int lineNumber = context->d()->lineNumber; + QV4::Function *function = qt_v4ExtractFunction(context); + QString engineName = function->sourceFile(); + + if (engineName.isEmpty()) + return; + + if (qt_v4IsStepping) { + if (qt_v4LastStop.lineNumber != lineNumber + || qt_v4LastStop.engineName != engineName) { + qt_v4IsStepping = false; + Breakpoint bp; + bp.bpNumber = 0; + bp.lineNumber = lineNumber; + bp.engineName = engineName; + qt_v4TriggerBreakpoint(bp, function); + return; + } + } + + for (int i = qt_v4Breakpoints.size(); --i >= 0; ) { + const Breakpoint &bp = qt_v4Breakpoints.at(i); + if (bp.lineNumber != lineNumber) + continue; + if (bp.engineName != engineName) + continue; + + qt_v4TriggerBreakpoint(bp, function); + } +} + +// End of debugger interface + using namespace QV4; using namespace QV4::Moth; @@ -126,6 +354,8 @@ QV4::ReturnedValue VME::run(ExecutionEngine *engine, const uchar *code qDebug("Starting VME with context=%p and code=%p", context, code); #endif // DO_TRACE_INSTR + qt_v4ResolvePendingBreakpointsHook(); + #ifdef MOTH_THREADED_INTERPRETER if (storeJumpTable) { #define MOTH_INSTR_ADDR(I, FMT) &&op_##I, @@ -620,10 +850,14 @@ QV4::ReturnedValue VME::run(ExecutionEngine *engine, const uchar *code QV4::Debugging::Debugger *debugger = context->engine()->debugger; if (debugger && debugger->pauseAtNextOpportunity()) debugger->maybeBreakAtInstruction(); + if (qt_v4IsDebugging) + qt_v4CheckForBreak(context, scopes, scopeDepth); MOTH_END_INSTR(Debug) MOTH_BEGIN_INSTR(Line) engine->currentContext()->lineNumber = instr.lineNumber; + if (qt_v4IsDebugging) + qt_v4CheckForBreak(context, scopes, scopeDepth); MOTH_END_INSTR(Debug) MOTH_BEGIN_INSTR(LoadThis) |