| 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 | |