| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qqmlnativedebugservice.h" | 
| 41 |  | 
| 42 | #include <private/qqmldebugconnector_p.h> | 
| 43 | #include <private/qv4debugging_p.h> | 
| 44 | #include <private/qv4engine_p.h> | 
| 45 | #include <private/qv4debugging_p.h> | 
| 46 | #include <private/qv4script_p.h> | 
| 47 | #include <private/qv4string_p.h> | 
| 48 | #include <private/qv4objectiterator_p.h> | 
| 49 | #include <private/qv4identifier_p.h> | 
| 50 | #include <private/qv4runtime_p.h> | 
| 51 | #include <private/qversionedpacket_p.h> | 
| 52 | #include <private/qqmldebugserviceinterfaces_p.h> | 
| 53 | #include <private/qv4identifiertable_p.h> | 
| 54 |  | 
| 55 | #include <QtQml/qjsengine.h> | 
| 56 | #include <QtCore/qjsonarray.h> | 
| 57 | #include <QtCore/qjsondocument.h> | 
| 58 | #include <QtCore/qjsonobject.h> | 
| 59 | #include <QtCore/qjsonvalue.h> | 
| 60 | #include <QtCore/qvector.h> | 
| 61 | #include <QtCore/qpointer.h> | 
| 62 |  | 
| 63 | //#define TRACE_PROTOCOL(s) qDebug() << s | 
| 64 | #define TRACE_PROTOCOL(s) | 
| 65 |  | 
| 66 | QT_BEGIN_NAMESPACE | 
| 67 |  | 
| 68 | using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; | 
| 69 |  | 
| 70 | class BreakPoint | 
| 71 | { | 
| 72 | public: | 
| 73 |     BreakPoint() : id(-1), lineNumber(-1), enabled(false), ignoreCount(0), hitCount(0) {} | 
| 74 |     bool isValid() const { return lineNumber >= 0 && !fileName.isEmpty(); } | 
| 75 |  | 
| 76 |     int id; | 
| 77 |     int lineNumber; | 
| 78 |     QString fileName; | 
| 79 |     bool enabled; | 
| 80 |     QString condition; | 
| 81 |     int ignoreCount; | 
| 82 |  | 
| 83 |     int hitCount; | 
| 84 | }; | 
| 85 |  | 
| 86 | inline uint qHash(const BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW | 
| 87 | { | 
| 88 |     return qHash(key: b.fileName, seed) ^ b.lineNumber; | 
| 89 | } | 
| 90 |  | 
| 91 | inline bool operator==(const BreakPoint &a, const BreakPoint &b) | 
| 92 | { | 
| 93 |     return a.lineNumber == b.lineNumber && a.fileName == b.fileName | 
| 94 |             && a.enabled == b.enabled && a.condition == b.condition | 
| 95 |             && a.ignoreCount == b.ignoreCount; | 
| 96 | } | 
| 97 |  | 
| 98 | static void setError(QJsonObject *response, const QString &msg) | 
| 99 | { | 
| 100 |     response->insert(QStringLiteral("type" ), QStringLiteral("error" )); | 
| 101 |     response->insert(QStringLiteral("msg" ), value: msg); | 
| 102 | } | 
| 103 |  | 
| 104 | class NativeDebugger; | 
| 105 |  | 
| 106 | class Collector | 
| 107 | { | 
| 108 | public: | 
| 109 |     Collector(QV4::ExecutionEngine *engine) | 
| 110 |         : m_engine(engine), m_anonCount(0) | 
| 111 |     {} | 
| 112 |  | 
| 113 |     void collect(QJsonArray *output, const QString &parentIName, const QString &name, | 
| 114 |                  const QV4::Value &value); | 
| 115 |  | 
| 116 |     bool isExpanded(const QString &iname) const { return m_expanded.contains(str: iname); } | 
| 117 |  | 
| 118 | public: | 
| 119 |     QV4::ExecutionEngine *m_engine; | 
| 120 |     int m_anonCount; | 
| 121 |     QStringList m_expanded; | 
| 122 | }; | 
| 123 |  | 
| 124 | // Encapsulate Breakpoint handling | 
| 125 | // Could be made per-NativeDebugger (i.e. per execution engine, if needed) | 
| 126 | class BreakPointHandler | 
| 127 | { | 
| 128 | public: | 
| 129 |     BreakPointHandler() : m_haveBreakPoints(false), m_breakOnThrow(true), m_lastBreakpoint(1) {} | 
| 130 |  | 
| 131 |     void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments); | 
| 132 |     void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments); | 
| 133 |  | 
| 134 |     void removeBreakPoint(int id); | 
| 135 |     void enableBreakPoint(int id, bool onoff); | 
| 136 |  | 
| 137 |     void setBreakOnThrow(bool onoff); | 
| 138 |     bool m_haveBreakPoints; | 
| 139 |     bool m_breakOnThrow; | 
| 140 |     int m_lastBreakpoint; | 
| 141 |     QVector<BreakPoint> m_breakPoints; | 
| 142 | }; | 
| 143 |  | 
| 144 | void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments) | 
| 145 | { | 
| 146 |     TRACE_PROTOCOL("SET BREAKPOINT"  << arguments); | 
| 147 |     QString type = arguments.value(key: QLatin1String("type" )).toString(); | 
| 148 |  | 
| 149 |     QString fileName = arguments.value(key: QLatin1String("file" )).toString(); | 
| 150 |     if (fileName.isEmpty()) { | 
| 151 |         setError(response, QStringLiteral("breakpoint has no file name" )); | 
| 152 |         return; | 
| 153 |     } | 
| 154 |  | 
| 155 |     int line = arguments.value(key: QLatin1String("line" )).toInt(defaultValue: -1); | 
| 156 |     if (line < 0) { | 
| 157 |         setError(response, QStringLiteral("breakpoint has an invalid line number" )); | 
| 158 |         return; | 
| 159 |     } | 
| 160 |  | 
| 161 |     BreakPoint bp; | 
| 162 |     bp.id = m_lastBreakpoint++; | 
| 163 |     bp.fileName = fileName.mid(position: fileName.lastIndexOf(c: '/') + 1); | 
| 164 |     bp.lineNumber = line; | 
| 165 |     bp.enabled  = arguments.value(key: QLatin1String("enabled" )).toBool(defaultValue: true); | 
| 166 |     bp.condition = arguments.value(key: QLatin1String("condition" )).toString(); | 
| 167 |     bp.ignoreCount = arguments.value(key: QLatin1String("ignorecount" )).toInt(); | 
| 168 |     m_breakPoints.append(t: bp); | 
| 169 |  | 
| 170 |     m_haveBreakPoints = true; | 
| 171 |  | 
| 172 |     response->insert(QStringLiteral("type" ), value: type); | 
| 173 |     response->insert(QStringLiteral("breakpoint" ), value: bp.id); | 
| 174 | } | 
| 175 |  | 
| 176 | void BreakPointHandler::handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments) | 
| 177 | { | 
| 178 |     int id = arguments.value(key: QLatin1String("id" )).toInt(); | 
| 179 |     removeBreakPoint(id); | 
| 180 |     response->insert(QStringLiteral("id" ), value: id); | 
| 181 | } | 
| 182 |  | 
| 183 | class NativeDebugger : public QV4::Debugging::Debugger | 
| 184 | { | 
| 185 | public: | 
| 186 |     NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine); | 
| 187 |  | 
| 188 |     void signalEmitted(const QString &signal); | 
| 189 |  | 
| 190 |     QV4::ExecutionEngine *engine() const { return m_engine; } | 
| 191 |  | 
| 192 |     bool pauseAtNextOpportunity() const override { | 
| 193 |         return m_pauseRequested | 
| 194 |                 || m_service->m_breakHandler->m_haveBreakPoints | 
| 195 |                 || m_stepping >= StepOver; | 
| 196 |     } | 
| 197 |  | 
| 198 |     void maybeBreakAtInstruction() override; | 
| 199 |     void enteringFunction() override; | 
| 200 |     void leavingFunction(const QV4::ReturnedValue &retVal) override; | 
| 201 |     void aboutToThrow() override; | 
| 202 |  | 
| 203 |     void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments); | 
| 204 |  | 
| 205 | private: | 
| 206 |     void handleBacktrace(QJsonObject *response, const QJsonObject &arguments); | 
| 207 |     void handleVariables(QJsonObject *response, const QJsonObject &arguments); | 
| 208 |     void handleExpressions(QJsonObject *response, const QJsonObject &arguments); | 
| 209 |  | 
| 210 |     void handleDebuggerDeleted(QObject *debugger); | 
| 211 |  | 
| 212 |     QV4::ReturnedValue evaluateExpression(const QString &expression); | 
| 213 |     bool checkCondition(const QString &expression); | 
| 214 |  | 
| 215 |     QStringList breakOnSignals; | 
| 216 |  | 
| 217 |     enum Speed { | 
| 218 |         NotStepping = 0, | 
| 219 |         StepOut, | 
| 220 |         StepOver, | 
| 221 |         StepIn, | 
| 222 |     }; | 
| 223 |  | 
| 224 |     void pauseAndWait(); | 
| 225 |     void pause(); | 
| 226 |     void handleContinue(QJsonObject *reponse, Speed speed); | 
| 227 |  | 
| 228 |     QV4::Function *getFunction() const; | 
| 229 |  | 
| 230 |     bool reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber); | 
| 231 |  | 
| 232 |     QV4::ExecutionEngine *m_engine; | 
| 233 |     QQmlNativeDebugServiceImpl *m_service; | 
| 234 |     QV4::CppStackFrame *m_currentFrame = nullptr; | 
| 235 |     Speed m_stepping; | 
| 236 |     bool m_pauseRequested; | 
| 237 |     bool m_runningJob; | 
| 238 |  | 
| 239 |     QV4::PersistentValue m_returnedValue; | 
| 240 | }; | 
| 241 |  | 
| 242 | bool NativeDebugger::checkCondition(const QString &expression) | 
| 243 | { | 
| 244 |     QV4::Scope scope(m_engine); | 
| 245 |     QV4::ScopedValue r(scope, evaluateExpression(expression)); | 
| 246 |     return r->booleanValue(); | 
| 247 | } | 
| 248 |  | 
| 249 | QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression) | 
| 250 | { | 
| 251 |     QV4::Scope scope(m_engine); | 
| 252 |     m_runningJob = true; | 
| 253 |  | 
| 254 |     QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext() | 
| 255 |                                                              : m_engine->scriptContext(); | 
| 256 |  | 
| 257 |     QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression); | 
| 258 |     if (const QV4::Function *function = m_engine->currentStackFrame | 
| 259 |             ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) | 
| 260 |         script.strictMode = function->isStrict(); | 
| 261 |     // In order for property lookups in QML to work, we need to disable fast v4 lookups. | 
| 262 |     // That is a side-effect of inheritContext. | 
| 263 |     script.inheritContext = true; | 
| 264 |     script.parse(); | 
| 265 |     if (!m_engine->hasException) { | 
| 266 |         if (m_engine->currentStackFrame) { | 
| 267 |             QV4::ScopedValue thisObject(scope, m_engine->currentStackFrame->thisObject()); | 
| 268 |             script.run(thisObject); | 
| 269 |         } else { | 
| 270 |             script.run(); | 
| 271 |         } | 
| 272 |     } | 
| 273 |  | 
| 274 |     m_runningJob = false; | 
| 275 |     return QV4::Encode::undefined(); | 
| 276 | } | 
| 277 |  | 
| 278 | NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine) | 
| 279 |     : m_returnedValue(engine, QV4::Value::undefinedValue()) | 
| 280 | { | 
| 281 |     m_stepping = NotStepping; | 
| 282 |     m_pauseRequested = false; | 
| 283 |     m_runningJob = false; | 
| 284 |     m_service = service; | 
| 285 |     m_engine = engine; | 
| 286 |     TRACE_PROTOCOL("Creating native debugger" ); | 
| 287 | } | 
| 288 |  | 
| 289 | void NativeDebugger::signalEmitted(const QString &signal) | 
| 290 | { | 
| 291 |     //This function is only called by QQmlBoundSignal | 
| 292 |     //only if there is a slot connected to the signal. Hence, there | 
| 293 |     //is no need for additional check. | 
| 294 |  | 
| 295 |     //Parse just the name and remove the class info | 
| 296 |     //Normalize to Lower case. | 
| 297 |     QString signalName = signal.left(n: signal.indexOf(c: QLatin1Char('('))).toLower(); | 
| 298 |  | 
| 299 |     for (const QString &signal : qAsConst(t&: breakOnSignals)) { | 
| 300 |         if (signal == signalName) { | 
| 301 |             // TODO: pause debugger | 
| 302 |             break; | 
| 303 |         } | 
| 304 |     } | 
| 305 | } | 
| 306 |  | 
| 307 | void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd, | 
| 308 |                                    const QJsonObject &arguments) | 
| 309 | { | 
| 310 |     if (cmd == QLatin1String("backtrace" )) | 
| 311 |         handleBacktrace(response, arguments); | 
| 312 |     else if (cmd == QLatin1String("variables" )) | 
| 313 |         handleVariables(response, arguments); | 
| 314 |     else if (cmd == QLatin1String("expressions" )) | 
| 315 |         handleExpressions(response, arguments); | 
| 316 |     else if (cmd == QLatin1String("stepin" )) | 
| 317 |         handleContinue(reponse: response, speed: StepIn); | 
| 318 |     else if (cmd == QLatin1String("stepout" )) | 
| 319 |         handleContinue(reponse: response, speed: StepOut); | 
| 320 |     else if (cmd == QLatin1String("stepover" )) | 
| 321 |         handleContinue(reponse: response, speed: StepOver); | 
| 322 |     else if (cmd == QLatin1String("continue" )) | 
| 323 |         handleContinue(reponse: response, speed: NotStepping); | 
| 324 | } | 
| 325 |  | 
| 326 | static QString encodeFrame(QV4::CppStackFrame *f) | 
| 327 | { | 
| 328 |     QQmlDebugPacket ds; | 
| 329 |     ds << quintptr(f); | 
| 330 |     return QString::fromLatin1(str: ds.data().toHex()); | 
| 331 | } | 
| 332 |  | 
| 333 | static void decodeFrame(const QString &f, QV4::CppStackFrame **frame) | 
| 334 | { | 
| 335 |     quintptr rawFrame; | 
| 336 |     QQmlDebugPacket ds(QByteArray::fromHex(hexEncoded: f.toLatin1())); | 
| 337 |     ds >> rawFrame; | 
| 338 |     *frame = reinterpret_cast<QV4::CppStackFrame *>(rawFrame); | 
| 339 | } | 
| 340 |  | 
| 341 | void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments) | 
| 342 | { | 
| 343 |     int limit = arguments.value(key: QLatin1String("limit" )).toInt(defaultValue: 0); | 
| 344 |  | 
| 345 |     QJsonArray frameArray; | 
| 346 |     QV4::CppStackFrame *f= m_engine->currentStackFrame; | 
| 347 |     for (int i  = 0; i < limit && f; ++i) { | 
| 348 |         QV4::Function *function = f->v4Function; | 
| 349 |  | 
| 350 |         QJsonObject frame; | 
| 351 |         frame.insert(QStringLiteral("language" ), QStringLiteral("js" )); | 
| 352 |         frame.insert(QStringLiteral("context" ), value: encodeFrame(f)); | 
| 353 |  | 
| 354 |         if (QV4::Heap::String *functionName = function->name()) | 
| 355 |             frame.insert(QStringLiteral("function" ), value: functionName->toQString()); | 
| 356 |         frame.insert(QStringLiteral("file" ), value: function->sourceFile()); | 
| 357 |  | 
| 358 |         int line = f->lineNumber(); | 
| 359 |         frame.insert(QStringLiteral("line" ), value: (line < 0 ? -line : line)); | 
| 360 |  | 
| 361 |         frameArray.push_back(t: frame); | 
| 362 |  | 
| 363 |         f = f->parent; | 
| 364 |     } | 
| 365 |  | 
| 366 |     response->insert(QStringLiteral("frames" ), value: frameArray); | 
| 367 | } | 
| 368 |  | 
| 369 | void Collector::collect(QJsonArray *out, const QString &parentIName, const QString &name, | 
| 370 |                         const QV4::Value &value) | 
| 371 | { | 
| 372 |     QJsonObject dict; | 
| 373 |     QV4::Scope scope(m_engine); | 
| 374 |  | 
| 375 |     QString nonEmptyName = name.isEmpty() ? QString::fromLatin1(str: "@%1" ).arg(a: m_anonCount++) : name; | 
| 376 |     QString iname = parentIName + QLatin1Char('.') + nonEmptyName; | 
| 377 |     dict.insert(QStringLiteral("iname" ), value: iname); | 
| 378 |     dict.insert(QStringLiteral("name" ), value: nonEmptyName); | 
| 379 |  | 
| 380 |     QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(m_engine, value)); | 
| 381 |     dict.insert(QStringLiteral("type" ), value: typeString->toQStringNoThrow()); | 
| 382 |  | 
| 383 |     switch (value.type()) { | 
| 384 |     case QV4::Value::Empty_Type: | 
| 385 |         dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("empty" )); | 
| 386 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 387 |         break; | 
| 388 |     case QV4::Value::Undefined_Type: | 
| 389 |         dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("undefined" )); | 
| 390 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 391 |         break; | 
| 392 |     case QV4::Value::Null_Type: | 
| 393 |         dict.insert(QStringLiteral("type" ), QStringLiteral("object" )); | 
| 394 |         dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("null" )); | 
| 395 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 396 |         break; | 
| 397 |     case QV4::Value::Boolean_Type: | 
| 398 |         dict.insert(QStringLiteral("value" ), value: value.booleanValue()); | 
| 399 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 400 |         break; | 
| 401 |     case QV4::Value::Managed_Type: | 
| 402 |         if (const QV4::String *string = value.as<QV4::String>()) { | 
| 403 |             dict.insert(QStringLiteral("value" ), value: string->toQStringNoThrow()); | 
| 404 |             dict.insert(QStringLiteral("haschild" ), value: false); | 
| 405 |             dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("utf16" )); | 
| 406 |             dict.insert(QStringLiteral("quoted" ), value: true); | 
| 407 |         } else if (const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) { | 
| 408 |             // The size of an array is number of its numerical properties. | 
| 409 |             // We don't consider free form object properties here. | 
| 410 |             const uint n = array->getLength(); | 
| 411 |             dict.insert(QStringLiteral("value" ), value: qint64(n)); | 
| 412 |             dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("itemcount" )); | 
| 413 |             dict.insert(QStringLiteral("haschild" ), value: qint64(n)); | 
| 414 |             if (isExpanded(iname)) { | 
| 415 |                 QJsonArray children; | 
| 416 |                 for (uint i = 0; i < n; ++i) { | 
| 417 |                     QV4::ReturnedValue v = array->get(idx: i); | 
| 418 |                     QV4::ScopedValue sval(scope, v); | 
| 419 |                     collect(out: &children, parentIName: iname, name: QString::number(i), value: *sval); | 
| 420 |                 } | 
| 421 |                 dict.insert(QStringLiteral("children" ), value: children); | 
| 422 |             } | 
| 423 |         } else if (const QV4::Object *object = value.as<QV4::Object>()) { | 
| 424 |             QJsonArray children; | 
| 425 |             bool expanded = isExpanded(iname); | 
| 426 |             qint64 numProperties = 0; | 
| 427 |             QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); | 
| 428 |             QV4::ScopedProperty p(scope); | 
| 429 |             QV4::ScopedPropertyKey name(scope); | 
| 430 |             while (true) { | 
| 431 |                 QV4::PropertyAttributes attrs; | 
| 432 |                 name = it.next(pd: p, attributes: &attrs); | 
| 433 |                 if (!name->isValid()) | 
| 434 |                     break; | 
| 435 |                 if (name->isStringOrSymbol()) { | 
| 436 |                     ++numProperties; | 
| 437 |                     if (expanded) { | 
| 438 |                         QV4::Value v = p.property->value; | 
| 439 |                         collect(out: &children, parentIName: iname, name: name->toQString(), value: v); | 
| 440 |                     } | 
| 441 |                 } | 
| 442 |             } | 
| 443 |             dict.insert(QStringLiteral("value" ), value: numProperties); | 
| 444 |             dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("itemcount" )); | 
| 445 |             dict.insert(QStringLiteral("haschild" ), value: numProperties > 0); | 
| 446 |             if (expanded) | 
| 447 |                 dict.insert(QStringLiteral("children" ), value: children); | 
| 448 |         } | 
| 449 |         break; | 
| 450 |     case QV4::Value::Integer_Type: | 
| 451 |         dict.insert(QStringLiteral("value" ), value: value.integerValue()); | 
| 452 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 453 |         break; | 
| 454 |     default: // double | 
| 455 |         dict.insert(QStringLiteral("value" ), value: value.doubleValue()); | 
| 456 |         dict.insert(QStringLiteral("haschild" ), value: false); | 
| 457 |     } | 
| 458 |  | 
| 459 |     out->append(value: dict); | 
| 460 | } | 
| 461 |  | 
| 462 | void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &arguments) | 
| 463 | { | 
| 464 |     TRACE_PROTOCOL("Build variables" ); | 
| 465 |     QV4::CppStackFrame *frame = nullptr; | 
| 466 |     decodeFrame(f: arguments.value(key: QLatin1String("context" )).toString(), frame: &frame); | 
| 467 |     if (!frame) { | 
| 468 |         setError(response, QStringLiteral("No stack frame passed" )); | 
| 469 |         return; | 
| 470 |     } | 
| 471 |     TRACE_PROTOCOL("Context: "  << frame); | 
| 472 |  | 
| 473 |     QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine; | 
| 474 |     if (!engine) { | 
| 475 |         setError(response, QStringLiteral("No execution engine passed" )); | 
| 476 |         return; | 
| 477 |     } | 
| 478 |     TRACE_PROTOCOL("Engine: "  << engine); | 
| 479 |  | 
| 480 |     Collector collector(engine); | 
| 481 |     const QJsonArray expanded = arguments.value(key: QLatin1String("expanded" )).toArray(); | 
| 482 |     for (const QJsonValue &ex : expanded) | 
| 483 |         collector.m_expanded.append(t: ex.toString()); | 
| 484 |     TRACE_PROTOCOL("Expanded: "  << collector.m_expanded); | 
| 485 |  | 
| 486 |     QJsonArray output; | 
| 487 |     QV4::Scope scope(engine); | 
| 488 |  | 
| 489 |     QV4::ScopedValue thisObject(scope, frame->thisObject()); | 
| 490 |     collector.collect(out: &output, parentIName: QString(), QStringLiteral("this" ), value: thisObject); | 
| 491 |     QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext()); | 
| 492 |     if (callContext) { | 
| 493 |         QV4::Heap::InternalClass *ic = callContext->internalClass(); | 
| 494 |         QV4::ScopedValue v(scope); | 
| 495 |         for (uint i = 0; i < ic->size; ++i) { | 
| 496 |             QString name = ic->keyAt(index: i); | 
| 497 |             v = callContext->d()->locals[i]; | 
| 498 |             collector.collect(out: &output, parentIName: QString(), name, value: v); | 
| 499 |         } | 
| 500 |     } | 
| 501 |  | 
| 502 |     response->insert(QStringLiteral("variables" ), value: output); | 
| 503 | } | 
| 504 |  | 
| 505 | void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments) | 
| 506 | { | 
| 507 |     TRACE_PROTOCOL("Evaluate expressions" ); | 
| 508 |     QV4::CppStackFrame *frame = nullptr; | 
| 509 |     decodeFrame(f: arguments.value(key: QLatin1String("context" )).toString(), frame: &frame); | 
| 510 |     if (!frame) { | 
| 511 |         setError(response, QStringLiteral("No stack frame passed" )); | 
| 512 |         return; | 
| 513 |     } | 
| 514 |     TRACE_PROTOCOL("Context: "  << executionContext); | 
| 515 |  | 
| 516 |     QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine; | 
| 517 |     if (!engine) { | 
| 518 |         setError(response, QStringLiteral("No execution engine passed" )); | 
| 519 |         return; | 
| 520 |     } | 
| 521 |     TRACE_PROTOCOL("Engines: "  << engine << m_engine); | 
| 522 |  | 
| 523 |     Collector collector(engine); | 
| 524 |     const QJsonArray expanded = arguments.value(key: QLatin1String("expanded" )).toArray(); | 
| 525 |     for (const QJsonValue &ex : expanded) | 
| 526 |         collector.m_expanded.append(t: ex.toString()); | 
| 527 |     TRACE_PROTOCOL("Expanded: "  << collector.m_expanded); | 
| 528 |  | 
| 529 |     QJsonArray output; | 
| 530 |     QV4::Scope scope(engine); | 
| 531 |  | 
| 532 |     const QJsonArray expressions = arguments.value(key: QLatin1String("expressions" )).toArray(); | 
| 533 |     for (const QJsonValue &expr : expressions) { | 
| 534 |         QString expression = expr.toObject().value(key: QLatin1String("expression" )).toString(); | 
| 535 |         QString name = expr.toObject().value(key: QLatin1String("name" )).toString(); | 
| 536 |         TRACE_PROTOCOL("Evaluate expression: "  << expression); | 
| 537 |         m_runningJob = true; | 
| 538 |  | 
| 539 |         QV4::ScopedValue result(scope, evaluateExpression(expression)); | 
| 540 |  | 
| 541 |         m_runningJob = false; | 
| 542 |         if (result->isUndefined()) { | 
| 543 |             QJsonObject dict; | 
| 544 |             dict.insert(QStringLiteral("name" ), value: name); | 
| 545 |             dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("undefined" )); | 
| 546 |             output.append(value: dict); | 
| 547 |         } else if (result.ptr && result.ptr->rawValue()) { | 
| 548 |             collector.collect(out: &output, parentIName: QString(), name, value: *result); | 
| 549 |         } else { | 
| 550 |             QJsonObject dict; | 
| 551 |             dict.insert(QStringLiteral("name" ), value: name); | 
| 552 |             dict.insert(QStringLiteral("valueencoded" ), QStringLiteral("notaccessible" )); | 
| 553 |             output.append(value: dict); | 
| 554 |         } | 
| 555 |         TRACE_PROTOCOL("EXCEPTION: "  << engine->hasException); | 
| 556 |         engine->hasException = false; | 
| 557 |     } | 
| 558 |  | 
| 559 |     response->insert(QStringLiteral("expressions" ), value: output); | 
| 560 | } | 
| 561 |  | 
| 562 | void BreakPointHandler::removeBreakPoint(int id) | 
| 563 | { | 
| 564 |     for (int i = 0; i != m_breakPoints.size(); ++i) { | 
| 565 |         if (m_breakPoints.at(i).id == id) { | 
| 566 |             m_breakPoints.remove(i); | 
| 567 |             m_haveBreakPoints = !m_breakPoints.isEmpty(); | 
| 568 |             return; | 
| 569 |         } | 
| 570 |     } | 
| 571 | } | 
| 572 |  | 
| 573 | void BreakPointHandler::enableBreakPoint(int id, bool enabled) | 
| 574 | { | 
| 575 |     m_breakPoints[id].enabled = enabled; | 
| 576 | } | 
| 577 |  | 
| 578 | void NativeDebugger::pause() | 
| 579 | { | 
| 580 |     m_pauseRequested = true; | 
| 581 | } | 
| 582 |  | 
| 583 | void NativeDebugger::handleContinue(QJsonObject *response, Speed speed) | 
| 584 | { | 
| 585 |     Q_UNUSED(response); | 
| 586 |  | 
| 587 |     if (!m_returnedValue.isUndefined()) | 
| 588 |         m_returnedValue.set(engine: m_engine, value: QV4::Encode::undefined()); | 
| 589 |  | 
| 590 |     m_currentFrame = m_engine->currentStackFrame; | 
| 591 |     m_stepping = speed; | 
| 592 | } | 
| 593 |  | 
| 594 | void NativeDebugger::maybeBreakAtInstruction() | 
| 595 | { | 
| 596 |     if (m_runningJob) // do not re-enter when we're doing a job for the debugger. | 
| 597 |         return; | 
| 598 |  | 
| 599 |     if (m_stepping == StepOver) { | 
| 600 |         if (m_currentFrame == m_engine->currentStackFrame) | 
| 601 |             pauseAndWait(); | 
| 602 |         return; | 
| 603 |     } | 
| 604 |  | 
| 605 |     if (m_stepping == StepIn) { | 
| 606 |         pauseAndWait(); | 
| 607 |         return; | 
| 608 |     } | 
| 609 |  | 
| 610 |     if (m_pauseRequested) { // Serve debugging requests from the agent | 
| 611 |         m_pauseRequested = false; | 
| 612 |         pauseAndWait(); | 
| 613 |         return; | 
| 614 |     } | 
| 615 |  | 
| 616 |     if (m_service->m_breakHandler->m_haveBreakPoints) { | 
| 617 |         if (QV4::Function *function = getFunction()) { | 
| 618 |             // lineNumber will be negative for Ret instructions, so those won't match | 
| 619 |             const int lineNumber = m_engine->currentStackFrame->lineNumber(); | 
| 620 |             if (reallyHitTheBreakPoint(function, lineNumber)) | 
| 621 |                 pauseAndWait(); | 
| 622 |         } | 
| 623 |     } | 
| 624 | } | 
| 625 |  | 
| 626 | void NativeDebugger::enteringFunction() | 
| 627 | { | 
| 628 |     if (m_runningJob) | 
| 629 |         return; | 
| 630 |  | 
| 631 |     if (m_stepping == StepIn) { | 
| 632 |         m_currentFrame = m_engine->currentStackFrame; | 
| 633 |     } | 
| 634 | } | 
| 635 |  | 
| 636 | void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal) | 
| 637 | { | 
| 638 |     if (m_runningJob) | 
| 639 |         return; | 
| 640 |  | 
| 641 |     if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) { | 
| 642 |         m_currentFrame = m_currentFrame->parent; | 
| 643 |         m_stepping = StepOver; | 
| 644 |         m_returnedValue.set(engine: m_engine, value: retVal); | 
| 645 |     } | 
| 646 | } | 
| 647 |  | 
| 648 | void NativeDebugger::aboutToThrow() | 
| 649 | { | 
| 650 |     if (!m_service->m_breakHandler->m_breakOnThrow) | 
| 651 |         return; | 
| 652 |  | 
| 653 |     if (m_runningJob) // do not re-enter when we're doing a job for the debugger. | 
| 654 |         return; | 
| 655 |  | 
| 656 |     QJsonObject event; | 
| 657 |     // TODO: complete this! | 
| 658 |     event.insert(QStringLiteral("event" ), QStringLiteral("exception" )); | 
| 659 |     m_service->emitAsynchronousMessageToClient(message: event); | 
| 660 | } | 
| 661 |  | 
| 662 | QV4::Function *NativeDebugger::getFunction() const | 
| 663 | { | 
| 664 |     if (m_engine->currentStackFrame) | 
| 665 |         return m_engine->currentStackFrame->v4Function; | 
| 666 |     else | 
| 667 |         return m_engine->globalCode; | 
| 668 | } | 
| 669 |  | 
| 670 | void NativeDebugger::pauseAndWait() | 
| 671 | { | 
| 672 |     QJsonObject event; | 
| 673 |  | 
| 674 |     event.insert(QStringLiteral("event" ), QStringLiteral("break" )); | 
| 675 |     event.insert(QStringLiteral("language" ), QStringLiteral("js" )); | 
| 676 |     if (QV4::CppStackFrame *frame = m_engine->currentStackFrame) { | 
| 677 |         QV4::Function *function = frame->v4Function; | 
| 678 |         event.insert(QStringLiteral("file" ), value: function->sourceFile()); | 
| 679 |         int line = frame->lineNumber(); | 
| 680 |         event.insert(QStringLiteral("line" ), value: (line < 0 ? -line : line)); | 
| 681 |     } | 
| 682 |  | 
| 683 |     m_service->emitAsynchronousMessageToClient(message: event); | 
| 684 | } | 
| 685 |  | 
| 686 | bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber) | 
| 687 | { | 
| 688 |     for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) { | 
| 689 |         const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i); | 
| 690 |         if (bp.lineNumber == lineNumber) { | 
| 691 |             const QString base = QUrl(function->sourceFile()).fileName(); | 
| 692 |             if (bp.fileName.endsWith(s: base)) { | 
| 693 |                 if (bp.condition.isEmpty() || checkCondition(expression: bp.condition)) { | 
| 694 |                     BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i]; | 
| 695 |                     ++mbp.hitCount; | 
| 696 |                     if (mbp.hitCount > mbp.ignoreCount) | 
| 697 |                         return true; | 
| 698 |                 } | 
| 699 |             } | 
| 700 |         } | 
| 701 |     } | 
| 702 |     return false; | 
| 703 | } | 
| 704 |  | 
| 705 | QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent) | 
| 706 |     : QQmlNativeDebugService(1.0, parent) | 
| 707 | { | 
| 708 |     m_breakHandler = new BreakPointHandler; | 
| 709 | } | 
| 710 |  | 
| 711 | QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl() | 
| 712 | { | 
| 713 |     delete m_breakHandler; | 
| 714 | } | 
| 715 |  | 
| 716 | void QQmlNativeDebugServiceImpl::engineAboutToBeAdded(QJSEngine *engine) | 
| 717 | { | 
| 718 |     TRACE_PROTOCOL("Adding engine"  << engine); | 
| 719 |     if (engine) { | 
| 720 |         QV4::ExecutionEngine *ee = engine->handle(); | 
| 721 |         TRACE_PROTOCOL("Adding execution engine"  << ee); | 
| 722 |         if (ee) { | 
| 723 |             NativeDebugger *debugger = new NativeDebugger(this, ee); | 
| 724 |             if (state() == Enabled) | 
| 725 |                 ee->setDebugger(debugger); | 
| 726 |             m_debuggers.append(t: QPointer<NativeDebugger>(debugger)); | 
| 727 |         } | 
| 728 |     } | 
| 729 |     QQmlDebugService::engineAboutToBeAdded(engine); | 
| 730 | } | 
| 731 |  | 
| 732 | void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) | 
| 733 | { | 
| 734 |     TRACE_PROTOCOL("Removing engine"  << engine); | 
| 735 |     if (engine) { | 
| 736 |         QV4::ExecutionEngine *executionEngine = engine->handle(); | 
| 737 |         const auto debuggersCopy = m_debuggers; | 
| 738 |         for (NativeDebugger *debugger : debuggersCopy) { | 
| 739 |             if (debugger->engine() == executionEngine) | 
| 740 |                 m_debuggers.removeAll(t: debugger); | 
| 741 |         } | 
| 742 |     } | 
| 743 |     QQmlDebugService::engineAboutToBeRemoved(engine); | 
| 744 | } | 
| 745 |  | 
| 746 | void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state) | 
| 747 | { | 
| 748 |     if (state == Enabled) { | 
| 749 |         for (NativeDebugger *debugger : qAsConst(t&: m_debuggers)) { | 
| 750 |             QV4::ExecutionEngine *engine = debugger->engine(); | 
| 751 |             if (!engine->debugger()) | 
| 752 |                 engine->setDebugger(debugger); | 
| 753 |         } | 
| 754 |     } | 
| 755 |     QQmlDebugService::stateAboutToBeChanged(state); | 
| 756 | } | 
| 757 |  | 
| 758 | void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message) | 
| 759 | { | 
| 760 |     TRACE_PROTOCOL("Native message received: "  << message); | 
| 761 |     QJsonObject request = QJsonDocument::fromJson(json: message).object(); | 
| 762 |     QJsonObject response; | 
| 763 |     QJsonObject arguments = request.value(key: QLatin1String("arguments" )).toObject(); | 
| 764 |     QString cmd = request.value(key: QLatin1String("command" )).toString(); | 
| 765 |  | 
| 766 |     if (cmd == QLatin1String("setbreakpoint" )) { | 
| 767 |         m_breakHandler->handleSetBreakpoint(response: &response, arguments); | 
| 768 |     } else if (cmd == QLatin1String("removebreakpoint" )) { | 
| 769 |         m_breakHandler->handleRemoveBreakpoint(response: &response, arguments); | 
| 770 |     } else if (cmd == QLatin1String("echo" )) { | 
| 771 |         response.insert(QStringLiteral("result" ), value: arguments); | 
| 772 |     } else { | 
| 773 |         for (NativeDebugger *debugger : qAsConst(t&: m_debuggers)) | 
| 774 |             if (debugger) | 
| 775 |                 debugger->handleCommand(response: &response, cmd, arguments); | 
| 776 |     } | 
| 777 |     QJsonDocument doc; | 
| 778 |     doc.setObject(response); | 
| 779 |     QByteArray ba = doc.toJson(format: QJsonDocument::Compact); | 
| 780 |     TRACE_PROTOCOL("Sending synchronous response:"  << ba.constData() << endl); | 
| 781 |     emit messageToClient(name: s_key, message: ba); | 
| 782 | } | 
| 783 |  | 
| 784 | void QQmlNativeDebugServiceImpl::emitAsynchronousMessageToClient(const QJsonObject &message) | 
| 785 | { | 
| 786 |     QJsonDocument doc; | 
| 787 |     doc.setObject(message); | 
| 788 |     QByteArray ba = doc.toJson(format: QJsonDocument::Compact); | 
| 789 |     TRACE_PROTOCOL("Sending asynchronous message:"  << ba.constData() << endl); | 
| 790 |     emit messageToClient(name: s_key, message: ba); | 
| 791 | } | 
| 792 |  | 
| 793 | QT_END_NAMESPACE | 
| 794 |  |