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 test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest/QtTest>
29
30#include "qv4datacollector.h"
31#include "qv4debugger.h"
32#include "qv4debugjob.h"
33
34#include <QJSEngine>
35#include <QQmlEngine>
36#include <QQmlComponent>
37#include <private/qv4engine_p.h>
38#include <private/qv4debugging_p.h>
39#include <private/qv4objectiterator_p.h>
40#include <private/qv4string_p.h>
41#include <private/qqmlbuiltinfunctions_p.h>
42#include <private/qqmldebugservice_p.h>
43
44using namespace QV4;
45using namespace QV4::Debugging;
46
47typedef QV4::ReturnedValue (*InjectedFunction)(const FunctionObject *b, const QV4::Value *, const QV4::Value *, int);
48Q_DECLARE_METATYPE(InjectedFunction)
49
50static bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000)
51{
52 QEventLoop loop;
53 QObject::connect(sender: obj, signal, receiver: &loop, SLOT(quit()));
54 QTimer timer;
55 QSignalSpy timeoutSpy(&timer, SIGNAL(timeout()));
56 if (timeout > 0) {
57 QObject::connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit()));
58 timer.setSingleShot(true);
59 timer.start(msec: timeout);
60 }
61 loop.exec();
62 return timeoutSpy.isEmpty();
63}
64
65class TestEngine : public QJSEngine
66{
67 Q_OBJECT
68public:
69 TestEngine()
70 {
71 qMetaTypeId<InjectedFunction>();
72 }
73
74 Q_INVOKABLE void evaluate(const QString &script, const QString &fileName, int lineNumber = 1)
75 {
76 QJSEngine::evaluate(program: script, fileName, lineNumber);
77 emit evaluateFinished();
78 }
79
80 QV4::ExecutionEngine *v4Engine() { return handle(); }
81
82 Q_INVOKABLE void injectFunction(const QString &functionName, InjectedFunction injectedFunction)
83 {
84 QV4::ExecutionEngine *v4 = v4Engine();
85 QV4::Scope scope(v4);
86
87 QV4::ScopedString name(scope, v4->newString(s: functionName));
88 QV4::ScopedValue function(scope, FunctionObject::createBuiltinFunction(engine: v4, nameOrSymbol: name, code: injectedFunction, argumentCount: 0));
89 v4->globalObject->put(name, v: function);
90 }
91
92signals:
93 void evaluateFinished();
94};
95
96class ExceptionCollectJob: public CollectJob
97{
98 QV4DataCollector::Ref exception;
99
100public:
101 ExceptionCollectJob(QV4DataCollector *collector) :
102 CollectJob(collector), exception(-1) {}
103
104 void run() {
105 QV4::Scope scope(collector->engine());
106 QV4::ScopedValue v(scope, *collector->engine()->exceptionValue);
107 exception = collector->addValueRef(value: v);
108 }
109
110 QV4DataCollector::Ref exceptionValue() const { return exception; }
111};
112
113class TestAgent : public QObject
114{
115 Q_OBJECT
116public:
117 typedef QV4DataCollector::Refs Refs;
118 typedef QV4DataCollector::Ref Ref;
119 struct NamedRefs {
120 QJsonObject scope;
121
122 int size() const {
123 return scope.size();
124 }
125
126 bool contains(const QString &name) const {
127 return scope.contains(key: name);
128 }
129
130#define DUMP_JSON(x) {\
131 QJsonDocument doc(x);\
132 qDebug() << #x << "=" << doc.toJson(QJsonDocument::Indented);\
133}
134
135 QJsonObject rawValue(const QString &name) const {
136 Q_ASSERT(contains(name));
137 return scope[name].toObject();
138 }
139
140 QJsonValue value(const QString &name) const {
141 return rawValue(name).value(QStringLiteral("value"));
142 }
143
144 QString type(const QString &name) const {
145 return rawValue(name).value(QStringLiteral("type")).toString();
146 }
147
148 void dump(const QString &name) const {
149 if (!contains(name)) {
150 qDebug() << "no" << name;
151 return;
152 }
153
154 QJsonObject o = scope[name].toObject();
155 QJsonDocument d;
156 d.setObject(o);
157 qDebug() << name << "=" << d.toJson(format: QJsonDocument::Indented);
158 }
159 };
160
161 TestAgent(QV4::ExecutionEngine *engine)
162 : m_wasPaused(false)
163 , m_captureContextInfo(false)
164 , m_thrownValue(-1)
165 , collector(engine)
166 , m_resumeSpeed(QV4Debugger::FullThrottle)
167 , m_debugger(nullptr)
168 {
169 }
170
171public slots:
172 void debuggerPaused(QV4Debugger *debugger, QV4Debugger::PauseReason reason)
173 {
174 Q_ASSERT(debugger == m_debugger);
175 Q_ASSERT(debugger->engine() == collector.engine());
176 m_wasPaused = true;
177 m_pauseReason = reason;
178 m_statesWhenPaused << debugger->currentExecutionState();
179
180 if (debugger->state() == QV4Debugger::Paused && debugger->engine()->hasException) {
181 ExceptionCollectJob job(&collector);
182 debugger->runInEngine(job: &job);
183 m_thrownValue = job.exceptionValue();
184 }
185
186 foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused)
187 debugger->addBreakPoint(fileName: bp.fileName, lineNumber: bp.lineNumber);
188 m_breakPointsToAddWhenPaused.clear();
189
190 m_stackTrace = debugger->stackTrace();
191
192 while (!m_expressionRequests.isEmpty()) {
193 Q_ASSERT(debugger->state() == QV4Debugger::Paused);
194 ExpressionRequest request = m_expressionRequests.takeFirst();
195 ExpressionEvalJob job(debugger->engine(), request.frameNr, request.context,
196 request.expression, &collector);
197 debugger->runInEngine(job: &job);
198 m_expressionResults << job.returnValue();
199 }
200
201 if (m_captureContextInfo)
202 captureContextInfo(debugger);
203
204 debugger->resume(speed: m_resumeSpeed);
205 }
206
207public:
208 struct TestBreakPoint
209 {
210 TestBreakPoint() : lineNumber(-1) {}
211 TestBreakPoint(const QString &fileName, int lineNumber)
212 : fileName(fileName), lineNumber(lineNumber) {}
213 QString fileName;
214 int lineNumber;
215 };
216
217 void captureContextInfo(QV4Debugger *debugger)
218 {
219 for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) {
220 m_capturedScope.append(t: NamedRefs());
221 FrameJob frameJob(&collector, i);
222 debugger->runInEngine(job: &frameJob);
223 QJsonObject frameObj = frameJob.returnValue();
224 QJsonArray scopes = frameObj.value(key: QLatin1String("scopes")).toArray();
225 int nscopes = scopes.size();
226 int s = 0;
227 for (s = 0; s < nscopes; ++s) {
228 QJsonObject o = scopes.at(i: s).toObject();
229 if (o.value(key: QLatin1String("type")).toInt(defaultValue: -2) == 1) // CallContext
230 break;
231 }
232 if (s == nscopes)
233 return;
234
235 ScopeJob job(&collector, i, s);
236 debugger->runInEngine(job: &job);
237 NamedRefs &refs = m_capturedScope.last();
238 QJsonObject object = job.returnValue();
239 object = object.value(key: QLatin1String("object")).toObject();
240 QVERIFY(!object.contains("ref") || object.contains("properties"));
241 foreach (const QJsonValue &value, object.value(QLatin1String("properties")).toArray()) {
242 QJsonObject property = value.toObject();
243 QString name = property.value(key: QLatin1String("name")).toString();
244 property.remove(key: QLatin1String("name"));
245 refs.scope.insert(key: name, value: property);
246 }
247 }
248 }
249
250 void addDebugger(QV4Debugger *debugger)
251 {
252 Q_ASSERT(!m_debugger);
253 m_debugger = debugger;
254 connect(sender: m_debugger, signal: &QV4Debugger::debuggerPaused, receiver: this, slot: &TestAgent::debuggerPaused);
255 }
256
257 bool m_wasPaused;
258 QV4Debugger::PauseReason m_pauseReason;
259 bool m_captureContextInfo;
260 QList<QV4Debugger::ExecutionState> m_statesWhenPaused;
261 QList<TestBreakPoint> m_breakPointsToAddWhenPaused;
262 QVector<QV4::StackFrame> m_stackTrace;
263 QVector<NamedRefs> m_capturedScope;
264 qint64 m_thrownValue;
265 QV4DataCollector collector;
266
267 struct ExpressionRequest {
268 QString expression;
269 int frameNr;
270 int context;
271 };
272 QVector<ExpressionRequest> m_expressionRequests;
273 QV4Debugger::Speed m_resumeSpeed;
274 QList<QJsonObject> m_expressionResults;
275 QV4Debugger *m_debugger;
276
277 // Utility methods:
278 void dumpStackTrace() const
279 {
280 qDebug() << "Stack depth:" << m_stackTrace.size();
281 foreach (const QV4::StackFrame &frame, m_stackTrace)
282 qDebug(msg: "\t%s (%s:%d:%d)", qPrintable(frame.function), qPrintable(frame.source),
283 frame.line, frame.column);
284 }
285};
286
287class tst_qv4debugger : public QObject
288{
289 Q_OBJECT
290
291private slots:
292 void init();
293 void cleanup();
294
295 // breakpoints:
296 void breakAnywhere();
297 void pendingBreakpoint();
298 void liveBreakPoint();
299 void removePendingBreakPoint();
300 void addBreakPointWhilePaused();
301 void removeBreakPointForNextInstruction();
302 void conditionalBreakPoint();
303 void conditionalBreakPointInQml();
304
305 // context access:
306 void readArguments();
307 void readComplicatedArguments();
308 void readLocals();
309 void readObject();
310 void readContextInAllFrames();
311
312 // exceptions:
313 void pauseOnThrow();
314 void breakInCatch();
315 void breakInWith();
316
317 void evaluateExpression();
318 void stepToEndOfScript();
319
320 void lastLineOfConditional_data();
321 void lastLineOfConditional();
322
323 void readThis();
324 void signalParameters();
325
326private:
327 QV4Debugger *debugger() const
328 {
329 return static_cast<QV4Debugger *>(m_v4->debugger());
330 }
331 void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1)
332 {
333 QMetaObject::invokeMethod(obj: m_engine, member: "evaluate", type: Qt::QueuedConnection,
334 Q_ARG(QString, script), Q_ARG(QString, fileName),
335 Q_ARG(int, lineNumber));
336 waitForSignal(obj: m_engine, SIGNAL(evaluateFinished()), /*timeout*/0);
337 }
338
339 TestEngine *m_engine;
340 QV4::ExecutionEngine *m_v4;
341 TestAgent *m_debuggerAgent;
342 QThread *m_javaScriptThread;
343};
344
345void tst_qv4debugger::init()
346{
347 m_javaScriptThread = new QThread;
348 m_engine = new TestEngine;
349 m_v4 = m_engine->v4Engine();
350 m_v4->setDebugger(new QV4Debugger(m_v4));
351 m_engine->moveToThread(thread: m_javaScriptThread);
352 m_javaScriptThread->start();
353 m_debuggerAgent = new TestAgent(m_v4);
354 m_debuggerAgent->addDebugger(debugger: debugger());
355}
356
357void tst_qv4debugger::cleanup()
358{
359 m_javaScriptThread->exit();
360 m_javaScriptThread->wait();
361 delete m_engine;
362 delete m_javaScriptThread;
363 m_engine = nullptr;
364 m_v4 = nullptr;
365 delete m_debuggerAgent;
366 m_debuggerAgent = nullptr;
367}
368
369void tst_qv4debugger::breakAnywhere()
370{
371 QString script =
372 "var i = 42;\n"
373 "var j = i + 1\n"
374 "var k = i\n";
375 debugger()->pause();
376 evaluateJavaScript(script, fileName: "testFile");
377 QVERIFY(m_debuggerAgent->m_wasPaused);
378}
379
380void tst_qv4debugger::pendingBreakpoint()
381{
382 QString script =
383 "var i = 42;\n"
384 "var j = i + 1\n"
385 "var k = i\n";
386 debugger()->addBreakPoint(fileName: "testfile", lineNumber: 2);
387 evaluateJavaScript(script, fileName: "testfile");
388 QVERIFY(m_debuggerAgent->m_wasPaused);
389 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1);
390 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
391 QCOMPARE(state.fileName, QString("testfile"));
392 QCOMPARE(state.lineNumber, 2);
393}
394
395void tst_qv4debugger::liveBreakPoint()
396{
397 QString script =
398 "var i = 42;\n"
399 "var j = i + 1\n"
400 "var k = i\n";
401 m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("liveBreakPoint", 3);
402 debugger()->pause();
403 evaluateJavaScript(script, fileName: "liveBreakPoint");
404 QVERIFY(m_debuggerAgent->m_wasPaused);
405 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2);
406 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(i: 1);
407 QCOMPARE(state.fileName, QString("liveBreakPoint"));
408 QCOMPARE(state.lineNumber, 3);
409}
410
411void tst_qv4debugger::removePendingBreakPoint()
412{
413 QString script =
414 "var i = 42;\n"
415 "var j = i + 1\n"
416 "var k = i\n";
417 debugger()->addBreakPoint(fileName: "removePendingBreakPoint", lineNumber: 2);
418 debugger()->removeBreakPoint(fileName: "removePendingBreakPoint", lineNumber: 2);
419 evaluateJavaScript(script, fileName: "removePendingBreakPoint");
420 QVERIFY(!m_debuggerAgent->m_wasPaused);
421}
422
423void tst_qv4debugger::addBreakPointWhilePaused()
424{
425 QString script =
426 "var i = 42;\n"
427 "var j = i + 1\n"
428 "var k = i\n";
429 debugger()->addBreakPoint(fileName: "addBreakPointWhilePaused", lineNumber: 1);
430 m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("addBreakPointWhilePaused", 2);
431 evaluateJavaScript(script, fileName: "addBreakPointWhilePaused");
432 QVERIFY(m_debuggerAgent->m_wasPaused);
433 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2);
434
435 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(i: 0);
436 QCOMPARE(state.fileName, QString("addBreakPointWhilePaused"));
437 QCOMPARE(state.lineNumber, 1);
438
439 state = m_debuggerAgent->m_statesWhenPaused.at(i: 1);
440 QCOMPARE(state.fileName, QString("addBreakPointWhilePaused"));
441 QCOMPARE(state.lineNumber, 2);
442}
443
444static QV4::ReturnedValue someCall(const FunctionObject *function, const QV4::Value *, const QV4::Value *, int)
445{
446 static_cast<QV4Debugger *>(function->engine()->debugger())
447 ->removeBreakPoint(fileName: "removeBreakPointForNextInstruction", lineNumber: 2);
448 RETURN_UNDEFINED();
449}
450
451void tst_qv4debugger::removeBreakPointForNextInstruction()
452{
453 QString script =
454 "someCall();\n"
455 "var i = 42;";
456
457 QMetaObject::invokeMethod(obj: m_engine, member: "injectFunction", type: Qt::BlockingQueuedConnection,
458 Q_ARG(QString, "someCall"), Q_ARG(InjectedFunction, someCall));
459
460 debugger()->addBreakPoint(fileName: "removeBreakPointForNextInstruction", lineNumber: 2);
461
462 evaluateJavaScript(script, fileName: "removeBreakPointForNextInstruction");
463 QVERIFY(!m_debuggerAgent->m_wasPaused);
464}
465
466void tst_qv4debugger::conditionalBreakPoint()
467{
468 m_debuggerAgent->m_captureContextInfo = true;
469 QString script =
470 "var test = function() {\n"
471 " for (var i = 0; i < 15; ++i) {\n"
472 " var x = i;\n"
473 " }\n"
474 "}\n"
475 "test()\n";
476
477 debugger()->addBreakPoint(fileName: "conditionalBreakPoint", lineNumber: 3, QStringLiteral("i > 10"));
478 evaluateJavaScript(script, fileName: "conditionalBreakPoint");
479 QVERIFY(m_debuggerAgent->m_wasPaused);
480 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 4);
481 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
482 QCOMPARE(state.fileName, QString("conditionalBreakPoint"));
483 QCOMPARE(state.lineNumber, 3);
484
485 QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
486 const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(i: 0);
487 QCOMPARE(frame0.size(), 3);
488 QVERIFY(frame0.contains("i"));
489 QCOMPARE(frame0.value("i").toInt(), 11);
490}
491
492void tst_qv4debugger::conditionalBreakPointInQml()
493{
494 QQmlEngine engine;
495 QV4::ExecutionEngine *v4 = engine.handle();
496 QV4Debugger *v4Debugger = new QV4Debugger(v4);
497 v4->setDebugger(v4Debugger);
498
499 QScopedPointer<QThread> debugThread(new QThread);
500 debugThread->start();
501 QScopedPointer<TestAgent> debuggerAgent(new TestAgent(v4));
502 debuggerAgent->addDebugger(debugger: v4Debugger);
503 debuggerAgent->moveToThread(thread: debugThread.data());
504
505 QQmlComponent component(&engine);
506 component.setData("import QtQml 2.0\n"
507 "QtObject {\n"
508 " id: root\n"
509 " property int foo: 42\n"
510 " property bool success: false\n"
511 " Component.onCompleted: {\n"
512 " success = true;\n" // breakpoint here
513 " }\n"
514 "}\n", baseUrl: QUrl("test.qml"));
515
516 v4Debugger->addBreakPoint(fileName: "test.qml", lineNumber: 7, condition: "root.foo == 42");
517
518 QScopedPointer<QObject> obj(component.create());
519 QCOMPARE(obj->property("success").toBool(), true);
520
521 QCOMPARE(debuggerAgent->m_statesWhenPaused.count(), 1);
522 QCOMPARE(debuggerAgent->m_statesWhenPaused.at(0).fileName, QStringLiteral("test.qml"));
523 QCOMPARE(debuggerAgent->m_statesWhenPaused.at(0).lineNumber, 7);
524
525 debugThread->quit();
526 debugThread->wait();
527}
528
529void tst_qv4debugger::readArguments()
530{
531 m_debuggerAgent->m_captureContextInfo = true;
532 QString script =
533 "var f = function(a, b, c, d) {\n"
534 " return a === b\n"
535 "}\n"
536 "var four;\n"
537 "f(1, 'two', null, four);\n";
538 debugger()->addBreakPoint(fileName: "readArguments", lineNumber: 2);
539 evaluateJavaScript(script, fileName: "readArguments");
540 QVERIFY(m_debuggerAgent->m_wasPaused);
541 QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
542 const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(i: 0);
543 QCOMPARE(frame0.size(), 5);
544 QVERIFY(frame0.contains(QStringLiteral("a")));
545 QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number"));
546 QCOMPARE(frame0.value(QStringLiteral("a")).toDouble(), 1.0);
547 QVERIFY(frame0.scope.contains("b"));
548 QCOMPARE(frame0.type(QStringLiteral("b")), QStringLiteral("string"));
549 QCOMPARE(frame0.value(QStringLiteral("b")).toString(), QStringLiteral("two"));
550}
551
552void tst_qv4debugger::readComplicatedArguments()
553{
554 m_debuggerAgent->m_captureContextInfo = true;
555 QString script =
556 "var f = function(a) {\n"
557 " a = 12;\n"
558 " return a;\n"
559 "}\n"
560 "f(1, 2);\n";
561 debugger()->addBreakPoint(fileName: "readArguments", lineNumber: 3);
562 evaluateJavaScript(script, fileName: "readArguments");
563 QVERIFY(m_debuggerAgent->m_wasPaused);
564 QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
565 const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(i: 0);
566 QCOMPARE(frame0.size(), 2);
567 QVERIFY(frame0.contains(QStringLiteral("a")));
568 QCOMPARE(frame0.type(QStringLiteral("a")), QStringLiteral("number"));
569 QCOMPARE(frame0.value(QStringLiteral("a")).toInt(), 12);
570}
571
572void tst_qv4debugger::readLocals()
573{
574 m_debuggerAgent->m_captureContextInfo = true;
575 QString script =
576 "var f = function(a, b) {\n"
577 " var c = a + b\n"
578 " let e = 'jaja'\n"
579 " const ff = 'nenene'\n"
580 " var d = a - b\n" // breakpoint, c should be set, d should be undefined
581 " return c === d\n"
582 "}\n"
583 "f(1, 2, 3);\n";
584 debugger()->addBreakPoint(fileName: "readLocals", lineNumber: 5);
585 evaluateJavaScript(script, fileName: "readLocals");
586 QVERIFY(m_debuggerAgent->m_wasPaused);
587 QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
588 const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(i: 0);
589 QCOMPARE(frame0.size(), 7); // locals and parameters
590 QVERIFY(frame0.contains("c"));
591 QCOMPARE(frame0.type("c"), QStringLiteral("number"));
592 QCOMPARE(frame0.value("c").toDouble(), 3.0);
593 QVERIFY(frame0.contains("d"));
594 QCOMPARE(frame0.type("d"), QStringLiteral("undefined"));
595 QVERIFY(frame0.contains("e"));
596 QCOMPARE(frame0.type("e"), QStringLiteral("string"));
597 QCOMPARE(frame0.value("e").toString(), QStringLiteral("jaja"));
598 QVERIFY(frame0.contains("ff"));
599 QCOMPARE(frame0.type("ff"), QStringLiteral("string"));
600 QCOMPARE(frame0.value("ff").toString(), QStringLiteral("nenene"));
601}
602
603void tst_qv4debugger::readObject()
604{
605 m_debuggerAgent->m_captureContextInfo = true;
606 QString script =
607 "var f = function(a) {\n"
608 " var b = a\n"
609 " return b\n"
610 "}\n"
611 "f({head: 1, tail: { head: 'asdf', tail: null }});\n";
612 debugger()->addBreakPoint(fileName: "readObject", lineNumber: 3);
613 evaluateJavaScript(script, fileName: "readObject");
614 QVERIFY(m_debuggerAgent->m_wasPaused);
615 QVERIFY(m_debuggerAgent->m_capturedScope.size() > 1);
616 const TestAgent::NamedRefs &frame0 = m_debuggerAgent->m_capturedScope.at(i: 0);
617 QCOMPARE(frame0.size(), 3);
618 QVERIFY(frame0.contains("b"));
619 QCOMPARE(frame0.type("b"), QStringLiteral("object"));
620 QJsonObject b = frame0.rawValue(name: "b");
621 QVERIFY(b.contains(QStringLiteral("ref")));
622 QVERIFY(b.contains(QStringLiteral("value")));
623 QVERIFY(!b.contains(QStringLiteral("properties")));
624 QVERIFY(b.value("value").isDouble());
625 QCOMPARE(b.value("value").toInt(), 2);
626 b = m_debuggerAgent->collector.lookupRef(ref: b.value(key: "ref").toInt());
627 QVERIFY(b.contains(QStringLiteral("properties")));
628 QVERIFY(b.value("properties").isArray());
629 QJsonArray b_props = b.value(key: "properties").toArray();
630 QCOMPARE(b_props.size(), 2);
631
632 QVERIFY(b_props.at(0).isObject());
633 QJsonObject b_head = b_props.at(i: 0).toObject();
634 QCOMPARE(b_head.value("name").toString(), QStringLiteral("head"));
635 QCOMPARE(b_head.value("type").toString(), QStringLiteral("number"));
636 QCOMPARE(b_head.value("value").toDouble(), 1.0);
637 QVERIFY(b_props.at(1).isObject());
638 QJsonObject b_tail = b_props.at(i: 1).toObject();
639 QCOMPARE(b_tail.value("name").toString(), QStringLiteral("tail"));
640 QVERIFY(b_tail.contains("ref"));
641
642 QJsonObject b_tail_value = m_debuggerAgent->collector.lookupRef(ref: b_tail.value(key: "ref").toInt());
643 QCOMPARE(b_tail_value.value("type").toString(), QStringLiteral("object"));
644 QVERIFY(b_tail_value.contains("properties"));
645 QJsonArray b_tail_props = b_tail_value.value(key: "properties").toArray();
646 QCOMPARE(b_tail_props.size(), 2);
647 QJsonObject b_tail_head = b_tail_props.at(i: 0).toObject();
648 QCOMPARE(b_tail_head.value("name").toString(), QStringLiteral("head"));
649 QCOMPARE(b_tail_head.value("type").toString(), QStringLiteral("string"));
650 QCOMPARE(b_tail_head.value("value").toString(), QStringLiteral("asdf"));
651 QJsonObject b_tail_tail = b_tail_props.at(i: 1).toObject();
652 QCOMPARE(b_tail_tail.value("name").toString(), QStringLiteral("tail"));
653 QCOMPARE(b_tail_tail.value("type").toString(), QStringLiteral("object"));
654 QVERIFY(b_tail_tail.value("value").isNull());
655}
656
657void tst_qv4debugger::readContextInAllFrames()
658{
659 m_debuggerAgent->m_captureContextInfo = true;
660 QString script =
661 "var fact = function(n) {\n"
662 " if (n > 1) {\n"
663 " var n_1 = n - 1;\n"
664 " n_1 = fact(n_1);\n"
665 " return n * n_1;\n"
666 " } else\n"
667 " return 1;\n" // breakpoint
668 "}\n"
669 "fact(12);\n";
670 debugger()->addBreakPoint(fileName: "readFormalsInAllFrames", lineNumber: 7);
671 evaluateJavaScript(script, fileName: "readFormalsInAllFrames");
672 QVERIFY(m_debuggerAgent->m_wasPaused);
673 QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 13);
674 QCOMPARE(m_debuggerAgent->m_capturedScope.size(), 13);
675
676 for (int i = 0; i < 12; ++i) {
677 const TestAgent::NamedRefs &scope = m_debuggerAgent->m_capturedScope.at(i);
678 QCOMPARE(scope.size(), 3);
679 QVERIFY(scope.contains("n"));
680 QCOMPARE(scope.type("n"), QStringLiteral("number"));
681 QCOMPARE(scope.value("n").toDouble(), i + 1.0);
682 QVERIFY(scope.contains("n_1"));
683 if (i == 0) {
684 QCOMPARE(scope.type("n_1"), QStringLiteral("undefined"));
685 } else {
686 QCOMPARE(scope.type("n_1"), QStringLiteral("number"));
687 QCOMPARE(scope.value("n_1").toInt(), i);
688 }
689 }
690 QCOMPARE(m_debuggerAgent->m_capturedScope[12].size(), 0);
691}
692
693void tst_qv4debugger::pauseOnThrow()
694{
695 QString script =
696 "function die(n) {\n"
697 " throw n\n"
698 "}\n"
699 "die('hard');\n";
700 debugger()->setBreakOnThrow(true);
701 evaluateJavaScript(script, fileName: "pauseOnThrow");
702 QVERIFY(m_debuggerAgent->m_wasPaused);
703 QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::Throwing);
704 QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 2);
705 QVERIFY(m_debuggerAgent->m_thrownValue >= qint64(0));
706 QJsonObject exception = m_debuggerAgent->collector.lookupRef(ref: m_debuggerAgent->m_thrownValue);
707// DUMP_JSON(exception);
708 QCOMPARE(exception.value("type").toString(), QStringLiteral("string"));
709 QCOMPARE(exception.value("value").toString(), QStringLiteral("hard"));
710}
711
712void tst_qv4debugger::breakInCatch()
713{
714 QString script =
715 "try {\n"
716 " throw 'catch...'\n"
717 "} catch (e) {\n"
718 " console.log(e, 'me');\n"
719 "}\n";
720
721 debugger()->addBreakPoint(fileName: "breakInCatch", lineNumber: 4);
722 evaluateJavaScript(script, fileName: "breakInCatch");
723 QVERIFY(m_debuggerAgent->m_wasPaused);
724 QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::BreakPointHit);
725 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1);
726 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
727 QCOMPARE(state.fileName, QString("breakInCatch"));
728 QCOMPARE(state.lineNumber, 4);
729}
730
731void tst_qv4debugger::breakInWith()
732{
733 QString script =
734 "with (42) {\n"
735 " console.log('give the answer');\n"
736 "}\n";
737
738 debugger()->addBreakPoint(fileName: "breakInWith", lineNumber: 2);
739 evaluateJavaScript(script, fileName: "breakInWith");
740 QVERIFY(m_debuggerAgent->m_wasPaused);
741 QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::BreakPointHit);
742 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1);
743 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
744 QCOMPARE(state.fileName, QString("breakInWith"));
745 QCOMPARE(state.lineNumber, 2);
746}
747
748void tst_qv4debugger::evaluateExpression()
749{
750 QString script =
751 "function testFunction() {\n"
752 " var x = 10\n"
753 " return x\n" // breakpoint
754 "}\n"
755 "var x = 20\n"
756 "testFunction()\n";
757
758 TestAgent::ExpressionRequest request;
759 request.expression = "x";
760 request.frameNr = 0;
761 request.context = -1; // no extra context
762 m_debuggerAgent->m_expressionRequests << request;
763 request.expression = "x";
764 request.frameNr = 1;
765 m_debuggerAgent->m_expressionRequests << request;
766
767 request.context = 5355; // invalid context object
768 m_debuggerAgent->m_expressionRequests << request;
769
770 QObject object; // some object without QML context
771 request.context = QQmlDebugService::idForObject(&object);
772 m_debuggerAgent->m_expressionRequests << request;
773
774 debugger()->addBreakPoint(fileName: "evaluateExpression", lineNumber: 3);
775
776 evaluateJavaScript(script, fileName: "evaluateExpression");
777
778 QCOMPARE(m_debuggerAgent->m_expressionResults.count(), 4);
779 QJsonObject result0 = m_debuggerAgent->m_expressionResults[0];
780 QCOMPARE(result0.value("type").toString(), QStringLiteral("number"));
781 QCOMPARE(result0.value("value").toInt(), 10);
782 for (int i = 1; i < 4; ++i) {
783 QJsonObject result1 = m_debuggerAgent->m_expressionResults[i];
784 QCOMPARE(result1.value("type").toString(), QStringLiteral("number"));
785 QCOMPARE(result1.value("value").toInt(), 20);
786 }
787}
788
789void tst_qv4debugger::stepToEndOfScript()
790{
791 QString script =
792 "var ret = 0;\n"
793 "ret += 4;\n"
794 "ret += 1;\n"
795 "ret += 5;\n";
796
797 debugger()->addBreakPoint(fileName: "toEnd", lineNumber: 1);
798 m_debuggerAgent->m_resumeSpeed = QV4Debugger::StepOver;
799 evaluateJavaScript(script, fileName: "toEnd");
800 QVERIFY(m_debuggerAgent->m_wasPaused);
801 QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::Step);
802 QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 5);
803 for (int i = 0; i < 4; ++i) {
804 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(i);
805 QCOMPARE(state.fileName, QString("toEnd"));
806 QCOMPARE(state.lineNumber, i + 1);
807 }
808
809 QV4Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(i: 4);
810 QCOMPARE(state.fileName, QString("toEnd"));
811 QCOMPARE(state.lineNumber, -4); // A return instruction without proper line number.
812}
813
814void tst_qv4debugger::lastLineOfConditional_data()
815{
816 QTest::addColumn<QString>(name: "head");
817 QTest::addColumn<QString>(name: "tail");
818 QTest::addColumn<int>(name: "breakPoint");
819 QTest::addColumn<int>(name: "lastLine");
820
821 QTest::newRow(dataTag: "for {block}") << "for (var i = 0; i < 10; ++i) {\n" << "}" << 4 << 7;
822 QTest::newRow(dataTag: "for..in {block}") << "for (var i in [0, 1, 2, 3, 4]) {\n" << "}" << 4 << 7;
823 QTest::newRow(dataTag: "while {block}") << "while (ret < 10) {\n" << "}" << 4 << 7;
824 QTest::newRow(dataTag: "do..while {block}") << "do {\n" << "} while (ret < 10);" << 4 << 7;
825
826 QTest::newRow(dataTag: "if true {block}") << "if (true) {\n" << "}"
827 << 4 << 8;
828 QTest::newRow(dataTag: "if false {block}") << "if (false) {\n" << "}"
829 << 2 << 8;
830 QTest::newRow(dataTag: "if true else {block}") << "if (true) {\n" << "} else {\n ret += 8;\n}"
831 << 4 << 10;
832 QTest::newRow(dataTag: "if false else {block}") << "if (false) {\n" << "} else {\n ret += 8;\n}"
833 << 8 << 10;
834
835 QTest::newRow(dataTag: "for statement") << "for (var i = 0; i < 10; ++i)\n" << "" << 4 << 2;
836 QTest::newRow(dataTag: "for..in statement") << "for (var i in [0, 1, 2, 3, 4])\n" << "" << 4 << 2;
837 QTest::newRow(dataTag: "while statement") << "while (ret < 10)\n" << "" << 4 << 2;
838 QTest::newRow(dataTag: "do..while statement") << "do\n" << "while (ret < 10);" << 4 << 7;
839
840 // For two nested if statements without blocks, we need to map the jump from the inner to the
841 // outer one on the outer "if". There is just no better place.
842 QTest::newRow(dataTag: "if true statement") << "if (true)\n" << "" << 4 << 8;
843 QTest::newRow(dataTag: "if false statement") << "if (false)\n" << "" << 2 << 8;
844
845 // Also two nested ifs without blocks.
846 QTest::newRow(dataTag: "if true else statement") << "if (true)\n" << "else\n ret += 8;" << 4 << 9;
847 QTest::newRow(dataTag: "if false else statement") << "if (false)\n" << "else\n ret += 8;" << 8 << 9;
848}
849
850void tst_qv4debugger::lastLineOfConditional()
851{
852 QFETCH(QString, head);
853 QFETCH(QString, tail);
854 QFETCH(int, breakPoint);
855 QFETCH(int, lastLine);
856
857 QString script =
858 "var ret = 2;\n"
859 + head +
860 " if (ret == 2)\n"
861 " ret += 4;\n" // breakpoint, then step over
862 " else\n"
863 " ret += 1;\n"
864 + tail + "\n" +
865 "ret -= 5;";
866
867 debugger()->addBreakPoint(fileName: "trueBranch", lineNumber: breakPoint);
868 m_debuggerAgent->m_resumeSpeed = QV4Debugger::StepOver;
869 evaluateJavaScript(script, fileName: "trueBranch");
870 QVERIFY(m_debuggerAgent->m_wasPaused);
871 QCOMPARE(m_debuggerAgent->m_pauseReason, QV4Debugger::Step);
872 QVERIFY(m_debuggerAgent->m_statesWhenPaused.count() > 1);
873 QV4Debugger::ExecutionState firstState = m_debuggerAgent->m_statesWhenPaused.first();
874 QCOMPARE(firstState.fileName, QString("trueBranch"));
875 QCOMPARE(firstState.lineNumber, breakPoint);
876 QV4Debugger::ExecutionState secondState = m_debuggerAgent->m_statesWhenPaused.at(i: 1);
877 QCOMPARE(secondState.fileName, QString("trueBranch"));
878 QCOMPARE(secondState.lineNumber, lastLine);
879}
880
881void tst_qv4debugger::readThis()
882{
883 m_debuggerAgent->m_captureContextInfo = true;
884 QString script =
885 "var x = function() {\n"
886 " return this.a;\n"
887 "}.apply({a : 5}, []);\n";
888
889 TestAgent::ExpressionRequest request;
890 request.expression = "this";
891 request.frameNr = 0;
892 request.context = -1; // no extra context
893 m_debuggerAgent->m_expressionRequests << request;
894
895 debugger()->addBreakPoint(fileName: "applyThis", lineNumber: 2);
896 evaluateJavaScript(script, fileName: "applyThis");
897 QVERIFY(m_debuggerAgent->m_wasPaused);
898
899 QCOMPARE(m_debuggerAgent->m_expressionResults.count(), 1);
900 QJsonObject result0 = m_debuggerAgent->m_expressionResults[0];
901 QCOMPARE(result0.value("type").toString(), QStringLiteral("object"));
902 QCOMPARE(result0.value("value").toInt(), 1);
903 QJsonArray properties = result0.value(key: "properties").toArray();
904 QCOMPARE(properties.size(), 1);
905 QJsonObject a = properties.first().toObject();
906 QCOMPARE(a.value("name").toString(), QStringLiteral("a"));
907 QCOMPARE(a.value("type").toString(), QStringLiteral("number"));
908 QCOMPARE(a.value("value").toInt(), 5);
909}
910
911void tst_qv4debugger::signalParameters()
912{
913 QQmlEngine engine;
914 QV4::ExecutionEngine *v4 = engine.handle();
915 v4->setDebugger(new QV4Debugger(v4));
916
917 QQmlComponent component(&engine);
918 component.setData("import QtQml 2.12\n"
919 "QtObject {\n"
920 " id: root\n"
921 " property string result: 'unset'\n"
922 " property string resultCallbackInternal: 'unset'\n"
923 " property string resultCallbackExternal: 'unset'\n"
924 " signal signalWithArg(string textArg)\n"
925 " function call(callback) { callback(); }\n"
926 " function externalCallback() { root.resultCallbackExternal = textArg; }\n"
927 " property Connections connections : Connections {\n"
928 " target: root\n"
929 " onSignalWithArg: { root.result = textArg; call(function() { root.resultCallbackInternal = textArg; }); call(externalCallback); }\n"
930 " }\n"
931 " Component.onCompleted: signalWithArg('something')\n"
932 "}", baseUrl: QUrl("test.qml"));
933
934 QVERIFY2(component.isReady(), qPrintable(component.errorString()));
935 QScopedPointer<QObject> obj(component.create());
936 QVERIFY(obj);
937 QCOMPARE(obj->property("result").toString(), QLatin1String("something"));
938 QCOMPARE(obj->property("resultCallbackInternal").toString(), QLatin1String("something"));
939 QCOMPARE(obj->property("resultCallbackExternal").toString(), QLatin1String("unset"));
940}
941
942QTEST_MAIN(tst_qv4debugger)
943
944#include "tst_qv4debugger.moc"
945

source code of qtdeclarative/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp