From 9b6c20cb719542cfcf3e031afd5b2b0b1cd3833e Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 18 Apr 2019 15:37:17 +0200 Subject: Make JavaScript execution interruptible Add an atomic isInterrupted flag to BaseEngine and check that in addition to the hasException flag on checkException(). Add some more exception checks to cover all possible infinite loops. Also, remove the writeBarrierActive member from QV4::EngineBase. It isn't used. Fixes: QTBUG-49080 Change-Id: I86b3114e3e61aff3e5eb9b020749a908ed801c2b Reviewed-by: Simon Hausmann --- tests/auto/qml/qjsengine/tst_qjsengine.cpp | 79 ++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'tests/auto/qml') diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 9c3316e39f..6ca2663f30 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -245,6 +245,9 @@ private slots: void equality(); void aggressiveGc(); + void interrupt_data(); + void interrupt(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -4839,6 +4842,82 @@ void tst_QJSEngine::aggressiveGc() qputenv("QV4_MM_AGGRESSIVE_GC", origAggressiveGc); } +void tst_QJSEngine::interrupt_data() +{ + QTest::addColumn("jitThreshold"); + QTest::addColumn("code"); + + const int big = (1 << 24); + for (int i = 0; i <= big; i += big) { + const char *mode = i ? "interpret" : "jit"; + QTest::addRow("for with content / %s", mode) << i << "var a = 0; for (;;) { a += 2; }"; + QTest::addRow("for empty / %s", mode) << i << "for (;;) {}"; + QTest::addRow("for continue / %s", mode) << i << "for (;;) { continue; }"; + QTest::addRow("while with content / %s", mode) << i << "var a = 0; while (true) { a += 2; }"; + QTest::addRow("while empty / %s", mode) << i << "while (true) {}"; + QTest::addRow("while continue / %s", mode) << i << "while (true) { continue; }"; + QTest::addRow("do with content / %s", mode) << i << "var a = 0; do { a += 2; } while (true);"; + QTest::addRow("do empty / %s", mode) << i << "do {} while (true);"; + QTest::addRow("do continue / %s", mode) << i << "do { continue; } while (true);"; + QTest::addRow("nested loops / %s", mode) << i << "while (true) { for (;;) {} }"; + QTest::addRow("labeled continue / %s", mode) << i << "a: while (true) { for (;;) { continue a; } }"; + QTest::addRow("labeled break / %s", mode) << i << "while (true) { a: for (;;) { break a; } }"; + QTest::addRow("tail call / %s", mode) << i << "'use strict';\nfunction x() { return x(); }; x();"; + } +} + +class TemporaryJitThreshold +{ + Q_DISABLE_COPY_MOVE(TemporaryJitThreshold) +public: + TemporaryJitThreshold(int threshold) { + m_wasSet = qEnvironmentVariableIsSet(m_envVar); + m_value = qgetenv(m_envVar); + qputenv(m_envVar, QByteArray::number(threshold)); + } + + ~TemporaryJitThreshold() + { + if (m_wasSet) + qputenv(m_envVar, m_value); + else + qunsetenv(m_envVar); + } + +private: + const char *m_envVar = "QV4_JIT_CALL_THRESHOLD"; + bool m_wasSet = false; + QByteArray m_value; +}; + +void tst_QJSEngine::interrupt() +{ + QFETCH(int, jitThreshold); + QFETCH(QString, code); + + TemporaryJitThreshold threshold(jitThreshold); + Q_UNUSED(threshold); + + QJSEngine *engineInThread = nullptr; + QScopedPointer worker(QThread::create([&engineInThread, &code, jitThreshold](){ + QJSEngine jsEngine; + engineInThread = &jsEngine; + QJSValue result = jsEngine.evaluate(code); + QVERIFY(jsEngine.isInterrupted()); + QVERIFY(result.isError()); + QCOMPARE(result.toString(), QString::fromLatin1("Error: Interrupted")); + engineInThread = nullptr; + })); + worker->start(); + + QTRY_VERIFY(engineInThread); + + engineInThread->setInterrupted(true); + + QVERIFY(worker->wait()); + QVERIFY(!engineInThread); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" -- cgit v1.2.3