diff options
-rw-r--r-- | src/qml/doc/src/javascript/finetuning.qdoc | 5 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsbasicblocks.cpp | 143 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsbasicblocks_p.h | 19 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 12 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator_p.h | 2 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscompiler.cpp | 6 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscompiler_p.h | 9 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml | 9 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml | 13 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 39 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.cpp | 84 |
12 files changed, 245 insertions, 103 deletions
diff --git a/src/qml/doc/src/javascript/finetuning.qdoc b/src/qml/doc/src/javascript/finetuning.qdoc index e2d754eb83..fb13c0bab8 100644 --- a/src/qml/doc/src/javascript/finetuning.qdoc +++ b/src/qml/doc/src/javascript/finetuning.qdoc @@ -90,6 +90,11 @@ Running JavaScript code can be influenced by a few environment variables, partic \c {QV4_DUMP_BASIC_BLOCKS} is used as the path to the folder where the DOT files should be generated. If the path is any of ["-", "1", "true"] or if files can't be opened, the graphs are dumped to stdout instead. + \row + \li \c{QV4_VALIDATE_BASIC_BLOCKS} + \li Performs checks on the basic blocks of a function compiled ahead of time to validate + its structure and coherence. If the validation fails, an error message is printed to + the console. \endtable \l{The QML Disk Cache} accepts further environment variables that allow fine tuning its behavior. diff --git a/src/qmlcompiler/qqmljsbasicblocks.cpp b/src/qmlcompiler/qqmljsbasicblocks.cpp index 073e294f45..028f1df1c5 100644 --- a/src/qmlcompiler/qqmljsbasicblocks.cpp +++ b/src/qmlcompiler/qqmljsbasicblocks.cpp @@ -10,13 +10,15 @@ QT_BEGIN_NAMESPACE using namespace Qt::Literals::StringLiterals; DEFINE_BOOL_CONFIG_OPTION(qv4DumpBasicBlocks, QV4_DUMP_BASIC_BLOCKS) +DEFINE_BOOL_CONFIG_OPTION(qv4ValidateBasicBlocks, QV4_VALIDATE_BASIC_BLOCKS) void QQmlJSBasicBlocks::dumpBasicBlocks() { qDebug().noquote() << "=== Basic Blocks for \"%1\""_L1.arg(m_context->name); for (const auto &[blockOffset, block] : m_basicBlocks) { QDebug debug = qDebug().nospace(); - debug << "Block " << blockOffset << ":\n"; + debug << "Block " << (blockOffset < 0 ? "Function prolog"_L1 : QString::number(blockOffset)) + << ":\n"; debug << " jumpOrigins[" << block.jumpOrigins.size() << "]: "; for (auto origin : block.jumpOrigins) { debug << origin << ", "; @@ -31,6 +33,8 @@ void QQmlJSBasicBlocks::dumpBasicBlocks() } debug << "\n jumpTarget: " << block.jumpTarget; debug << "\n jumpIsUnConditional: " << block.jumpIsUnconditional; + debug << "\n isReturnBlock: " << block.isReturnBlock; + debug << "\n isThrowBlock: " << block.isThrowBlock; } qDebug() << "\n"; } @@ -43,32 +47,29 @@ void QQmlJSBasicBlocks::dumpDOTGraph() "   to preserve formatting)\n"_L1.arg(m_context->name); s << "digraph BasicBlocks {\n"_L1; - std::map<int, BasicBlock> blocks{ m_basicBlocks.begin(), m_basicBlocks.end() }; + QFlatMap<int, BasicBlock> blocks{ m_basicBlocks }; for (const auto &[blockOffset, block] : blocks) { for (int originOffset : block.jumpOrigins) { - int originBlockOffset; - auto originBlockIt = blocks.find(originOffset); - bool isBackEdge = false; - if (originBlockIt != blocks.end()) { - originBlockOffset = originOffset; - isBackEdge = originOffset > blockOffset - && originBlockIt->second.jumpIsUnconditional; - } else { - originBlockOffset = std::prev(blocks.lower_bound(originOffset))->first; - } - s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockOffset)) + const auto originBlockIt = basicBlockForInstruction(blocks, originOffset); + const auto isBackEdge = originOffset > blockOffset && originBlockIt->second.jumpIsUnconditional; + s << " %1 -> %2%3\n"_L1.arg(QString::number(originBlockIt.key())) .arg(QString::number(blockOffset)) .arg(isBackEdge ? " [color=blue]"_L1 : ""_L1); } } for (const auto &[blockOffset, block] : blocks) { - int beginOffset = std::max(0, blockOffset); + if (blockOffset < 0) { + s << " %1 [shape=record, fontname=\"Monospace\", label=\"Function Prolog\"]\n"_L1 + .arg(QString::number(blockOffset)); + continue; + } + auto nextBlockIt = blocks.lower_bound(blockOffset + 1); int nextBlockOffset = nextBlockIt == blocks.end() ? m_context->code.size() : nextBlockIt->first; QString dump = QV4::Moth::dumpBytecode( m_context->code.constData(), m_context->code.size(), m_context->locals.size(), - m_context->formals->length(), beginOffset, nextBlockOffset - 1, + m_context->formals->length(), blockOffset, nextBlockOffset - 1, m_context->lineAndStatementNumberMapping); dump = dump.replace(" "_L1, " "_L1); // prevent collapse of extra whitespace for formatting dump = dump.replace("\n"_L1, "\\l"_L1); // new line + left aligned @@ -108,14 +109,15 @@ void deduplicate(Container &container) container.erase(erase, container.end()); } -QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run( - const Function *function, - const InstructionAnnotations &annotations, - QQmlJS::DiagnosticMessage *error) +QQmlJSCompilePass::InstructionAnnotations +QQmlJSBasicBlocks::run(const Function *function, const InstructionAnnotations &annotations, + QQmlJS::DiagnosticMessage *error, QQmlJSAotCompiler::Flags compileFlags, + bool &basicBlocksValidationFailed) { m_function = function; m_annotations = annotations; m_error = error; + basicBlocksValidationFailed = false; for (int i = 0, end = function->argumentTypes.size(); i != end; ++i) { InstructionAnnotation annotation; @@ -131,7 +133,11 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run( m_annotations[-annotation.changedRegisterIndex] = annotation; } + // Insert the function prolog block followed by the first "real" block. m_basicBlocks.insert_or_assign(m_annotations.begin().key(), BasicBlock()); + BasicBlock zeroBlock; + zeroBlock.jumpOrigins.append(m_basicBlocks.begin().key()); + m_basicBlocks.insert(0, zeroBlock); const QByteArray byteCode = function->code; decode(byteCode.constData(), static_cast<uint>(byteCode.size())); @@ -154,6 +160,13 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSBasicBlocks::run( deduplicate(it->second.jumpOrigins); } + if (compileFlags.testFlag(QQmlJSAotCompiler::ValidateBasicBlocks) || qv4ValidateBasicBlocks()) { + if (auto validationResult = basicBlocksValidation(); !validationResult.success) { + qDebug() << "Basic blocks validation failed: %1."_L1.arg(validationResult.errorMessage); + basicBlocksValidationFailed = true; + } + } + populateBasicBlocks(); populateReaderLocations(); adjustTypes(); @@ -214,11 +227,15 @@ void QQmlJSBasicBlocks::generate_JumpNotUndefined(int offset) void QQmlJSBasicBlocks::generate_Ret() { + auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset()); + currentBlock.value().isReturnBlock = true; m_skipUntilNextLabel = true; } void QQmlJSBasicBlocks::generate_ThrowException() { + auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset()); + currentBlock.value().isThrowBlock = true; m_skipUntilNextLabel = true; } @@ -246,9 +263,7 @@ void QQmlJSBasicBlocks::processJump(int offset, JumpMode mode) m_hadBackJumps = true; const int jumpTarget = absoluteOffset(offset); Q_ASSERT(!m_basicBlocks.isEmpty()); - auto currentBlock = m_basicBlocks.lower_bound(currentInstructionOffset()); - if (currentBlock == m_basicBlocks.end() || currentBlock->first != currentInstructionOffset()) - --currentBlock; + auto currentBlock = basicBlockForInstruction(m_basicBlocks, currentInstructionOffset()); currentBlock->second.jumpTarget = jumpTarget; currentBlock->second.jumpIsUnconditional = (mode == Unconditional); m_basicBlocks[jumpTarget].jumpOrigins.append(currentInstructionOffset()); @@ -375,10 +390,7 @@ void QQmlJSBasicBlocks::populateReaderLocations() m_typeResolver->trackedContainedType(writeIt->second.changedRegister)); } - auto blockIt = m_basicBlocks.lower_bound(writeIt.key()); - if (blockIt == m_basicBlocks.end() || blockIt->first != writeIt.key()) - --blockIt; - + auto blockIt = basicBlockForInstruction(m_basicBlocks, writeIt.key()); QList<PendingBlock> blocks = { { {}, blockIt->first, true } }; QHash<int, PendingBlock> processedBlocks; bool isFirstBlock = true; @@ -494,21 +506,31 @@ void QQmlJSBasicBlocks::populateReaderLocations() } } +QFlatMap<int, QQmlJSBasicBlocks::BasicBlock>::iterator +QQmlJSBasicBlocks::basicBlockForInstruction(QFlatMap<int, BasicBlock> &container, + int instructionOffset) +{ + auto block = container.lower_bound(instructionOffset); + if (block == container.end() || block->first != instructionOffset) + --block; + return block; +} + +QFlatMap<int, QQmlJSBasicBlocks::BasicBlock>::const_iterator +QQmlJSBasicBlocks::basicBlockForInstruction(const QFlatMap<int, BasicBlock> &container, + int instructionOffset) const +{ + auto *nonConstThis = const_cast<QQmlJSBasicBlocks *>(this); + return nonConstThis->basicBlockForInstruction( + const_cast<QFlatMap<int, BasicBlock> &>(container), instructionOffset); +} + bool QQmlJSBasicBlocks::canMove(int instructionOffset, const RegisterAccess &access) const { if (access.registerReadersAndConversions.size() != 1) return false; - const auto basicBlockForInstruction = [this](int instruction) { - auto block = m_basicBlocks.lower_bound(instruction); - if (block == m_basicBlocks.end() || block.key() == instruction) - return block; - Q_ASSERT(block.key() > instruction); - if (block == m_basicBlocks.begin()) - return m_basicBlocks.end(); - return --block; - }; - return basicBlockForInstruction(instructionOffset) - == basicBlockForInstruction(access.registerReadersAndConversions.begin().key()); + return basicBlockForInstruction(m_basicBlocks, instructionOffset) + == basicBlockForInstruction(m_basicBlocks, access.registerReadersAndConversions.begin().key()); } static QString adjustErrorMessage( @@ -743,4 +765,51 @@ void QQmlJSBasicBlocks::populateBasicBlocks() } } +QQmlJSBasicBlocks::BasicBlocksValidationResult QQmlJSBasicBlocks::basicBlocksValidation() +{ + if (m_basicBlocks.empty()) + return {}; + + const QFlatMap<int, BasicBlock> blocks{ m_basicBlocks }; + QList<QFlatMap<int, BasicBlock>::const_iterator> returnOrThrowBlocks; + for (auto it = blocks.cbegin(); it != blocks.cend(); ++it) { + if (it.value().isReturnBlock || it.value().isThrowBlock) + returnOrThrowBlocks.append(it); + } + + // 1. Return blocks and throw blocks must not have a jump target + for (const auto it : returnOrThrowBlocks) { + if (it.value().jumpTarget != -1) + return { false, "Return or throw block jumps to somewhere"_L1 }; + } + + // 2. The basic blocks graph must be connected. + QSet<int> visitedBlockOffsets; + QList<QFlatMap<int, BasicBlock>::const_iterator> toVisit; + toVisit.append(returnOrThrowBlocks); + + while (!toVisit.empty()) { + const auto &[offset, block] = *toVisit.takeLast(); + visitedBlockOffsets.insert(offset); + for (int originOffset : block.jumpOrigins) { + const auto originBlock = basicBlockForInstruction(blocks, originOffset); + if (visitedBlockOffsets.find(originBlock.key()) == visitedBlockOffsets.end() + && !toVisit.contains(originBlock)) + toVisit.append(originBlock); + } + } + + if (visitedBlockOffsets.size() != blocks.size()) + return { false, "Basic blocks graph is not fully connected"_L1 }; + + // 3. A block's jump target must be the first offset of a block. + for (const auto &[blockOffset, block] : blocks) { + auto target = block.jumpTarget; + if (target != -1 && blocks.find(target) == blocks.end()) + return { false, "Invalid jump; target is not the start of a block"_L1 }; + } + + return {}; +} + QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmljsbasicblocks_p.h b/src/qmlcompiler/qqmljsbasicblocks_p.h index b641928f32..47c55bfdf2 100644 --- a/src/qmlcompiler/qqmljsbasicblocks_p.h +++ b/src/qmlcompiler/qqmljsbasicblocks_p.h @@ -15,8 +15,9 @@ // We mean it. -#include <private/qqmljscompilepass_p.h> #include <private/qflatmap_p.h> +#include <private/qqmljscompilepass_p.h> +#include <private/qqmljscompiler_p.h> QT_BEGIN_NAMESPACE @@ -29,6 +30,8 @@ public: QList<QQmlJSScope::ConstPtr> readTypes; int jumpTarget = -1; bool jumpIsUnconditional = false; + bool isReturnBlock = false; + bool isThrowBlock = false; }; QQmlJSBasicBlocks(const QV4::Compiler::Context *context, @@ -40,9 +43,12 @@ public: ~QQmlJSBasicBlocks() = default; - InstructionAnnotations run( - const Function *function, const InstructionAnnotations &annotations, - QQmlJS::DiagnosticMessage *error); + InstructionAnnotations run(const Function *function, const InstructionAnnotations &annotations, + QQmlJS::DiagnosticMessage *error, QQmlJSAotCompiler::Flags, + bool &basicBlocksValidationFailed); + + struct BasicBlocksValidationResult { bool success = true; QString errorMessage; }; + BasicBlocksValidationResult basicBlocksValidation(); private: struct RegisterAccess @@ -85,6 +91,11 @@ private: void adjustTypes(); bool canMove(int instructionOffset, const RegisterAccess &access) const; + QFlatMap<int, BasicBlock>::iterator + basicBlockForInstruction(QFlatMap<int, BasicBlock> &container, int instructionOffset); + QFlatMap<int, BasicBlock>::const_iterator + basicBlockForInstruction(const QFlatMap<int, BasicBlock> &container, int instructionOffset) const; + void dumpBasicBlocks(); void dumpDOTGraph(); diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index f67cf12974..2fbc265672 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -93,9 +93,10 @@ QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type) : metaTypeFromName(type); } -QQmlJSAotFunction QQmlJSCodeGenerator::run( - const Function *function, const InstructionAnnotations *annotations, - QQmlJS::DiagnosticMessage *error) +QQmlJSAotFunction QQmlJSCodeGenerator::run(const Function *function, + const InstructionAnnotations *annotations, + QQmlJS::DiagnosticMessage *error, + bool basicBlocksValidationFailed) { m_annotations = annotations; m_function = function; @@ -150,6 +151,11 @@ QT_WARNING_POP QQmlJSAotFunction result; result.includes.swap(m_includes); + if (basicBlocksValidationFailed) { + result.code += "// QV4_BASIC_BLOCK_VALIDATION_FAILED: This file failed compilation "_L1 + "with basic blocks validation but compiled without it.\n"_L1; + } + result.code += u"// %1 at line %2, column %3\n"_s .arg(m_context->name).arg(m_context->line).arg(m_context->column); diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h index 4268416568..c2705e6f32 100644 --- a/src/qmlcompiler/qqmljscodegenerator_p.h +++ b/src/qmlcompiler/qqmljscodegenerator_p.h @@ -37,7 +37,7 @@ public: ~QQmlJSCodeGenerator() = default; QQmlJSAotFunction run(const Function *function, const InstructionAnnotations *annotations, - QQmlJS::DiagnosticMessage *error); + QQmlJS::DiagnosticMessage *error, bool basicBlocksValidationFailed); protected: struct CodegenState : public State diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index 314e241520..9dfe7010b0 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -763,7 +763,6 @@ QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const return global; } - QQmlJSAotFunction QQmlJSAotCompiler::doCompile( const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function, QQmlJS::DiagnosticMessage *error) @@ -784,8 +783,9 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile( if (error->isValid()) return compileError(); + bool basicBlocksValidationFailed = false; QQmlJSBasicBlocks basicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger); - typePropagationResult = basicBlocks.run(function, typePropagationResult, error); + typePropagationResult = basicBlocks.run(function, typePropagationResult, error, m_flags, basicBlocksValidationFailed); if (error->isValid()) return compileError(); @@ -798,7 +798,7 @@ QQmlJSAotFunction QQmlJSAotCompiler::doCompile( QQmlJSCodeGenerator codegen( context, m_unitGenerator, &m_typeResolver, m_logger); - QQmlJSAotFunction result = codegen.run(function, &typePropagationResult, error); + QQmlJSAotFunction result = codegen.run(function, &typePropagationResult, error, basicBlocksValidationFailed); return error->isValid() ? compileError() : result; } diff --git a/src/qmlcompiler/qqmljscompiler_p.h b/src/qmlcompiler/qqmljscompiler_p.h index 9f6afe0fc5..94c2f6283c 100644 --- a/src/qmlcompiler/qqmljscompiler_p.h +++ b/src/qmlcompiler/qqmljscompiler_p.h @@ -56,6 +56,12 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSAotFunction class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSAotCompiler { public: + enum Flag { + NoFlags = 0x0, + ValidateBasicBlocks = 0x1, + }; + Q_DECLARE_FLAGS(Flags, Flag) + QQmlJSAotCompiler(QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles, QQmlJSLogger *logger); @@ -71,6 +77,8 @@ public: virtual QQmlJSAotFunction globalCode() const; + Flags m_flags; + protected: virtual QQmlJS::DiagnosticMessage diagnose( const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location) const; @@ -94,6 +102,7 @@ private: QQmlJS::DiagnosticMessage *error); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlJSAotCompiler::Flags); using QQmlJSAotFunctionMap = QMap<int, QQmlJSAotFunction>; using QQmlJSSaveFunction diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 55cf009d93..de0ca38122 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -67,6 +67,7 @@ set(qml_files attachedBaseEnum.qml badSequence.qml basicBlocksWithBackJump.qml + basicBlocksWithBackJump_infinite.qml basicDTZ.qml bindToValueType.qml blockComments.qml @@ -262,10 +263,15 @@ qt_autogen_tools_initial_setup(codegen_test_module) set_target_properties(codegen_test_module PROPERTIES # We really want qmlcachegen here, even if qmlsc is available QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks ) qt_policy(SET QTP0001 NEW) +target_compile_definitions(codegen_test_module PUBLIC + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" +) + qt6_add_qml_module(codegen_test_module VERSION 1.5 URI TestTypes @@ -294,6 +300,7 @@ qt_autogen_tools_initial_setup(codegen_test_module_verify) set_target_properties(codegen_test_module_verify PROPERTIES # We really want qmlcachegen here, even if qmlsc is available QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks ) diff --git a/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml index 4c910637d1..5b254fe494 100644 --- a/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml +++ b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml @@ -2,15 +2,6 @@ pragma Strict import QtQml QtObject { - function infinite() { - let foo = false - if (true) { - while (true) {} - } else { - console.log(foo) - } - } - function t1() { let i = 0 let foo = false diff --git a/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml new file mode 100644 index 0000000000..997ff68736 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml @@ -0,0 +1,13 @@ +pragma Strict +import QtQml + +QtObject { + function infinite() { + let foo = false + if (true) { + while (true) {} + } else { + console.log(foo) + } + } +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 0c9648a19d..db1ae4f08b 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -30,6 +30,7 @@ class tst_QmlCppCodegen : public QObject Q_OBJECT private slots: void initTestCase(); + void cleanupTestCase(); void accessModelMethodFromOutSide(); void aliasLookup(); void ambiguousAs(); @@ -43,6 +44,7 @@ private slots: void attachedType(); void badSequence(); void basicBlocksWithBackJump(); + void basicBlocksWithBackJump_infinite(); void basicDTZ(); void bindToValueType(); void bindingExpression(); @@ -458,6 +460,34 @@ void tst_QmlCppCodegen::initTestCase() #endif } +void tst_QmlCppCodegen::cleanupTestCase() +{ + // This code checks for basic blocks validation failures in the tests + QStringList expectedFailures = { + "codegen_test_module_basicBlocksWithBackJump_infinite_qml.cpp", + "codegen_test_module_verify_basicBlocksWithBackJump_infinite_qml.cpp", + }; + + QString generatedCppFolder = GENERATED_CPP_FOLDER; + QDirIterator dirIterator(generatedCppFolder, { "*.cpp" }, QDir::Files); + while (dirIterator.hasNext()) { + QFile file(dirIterator.next()); + if (!file.open(QIODeviceBase::ReadOnly | QIODeviceBase::Text)) { + qDebug() << "Couldn't open generated file"; + continue; + } + + const auto content = file.readAll(); + if (bool validationFailed = content.contains("// QV4_BASIC_BLOCK_VALIDATION_FAILED:"_L1)) { + if (expectedFailures.contains(dirIterator.fileInfo().fileName())) { + QEXPECT_FAIL("", "Expected failure", Continue); + } + const auto message = file.fileName() + ": Basic blocks validation failed."; + QVERIFY2(!validationFailed, message.toStdString().c_str()); + } + } +} + void tst_QmlCppCodegen::accessModelMethodFromOutSide() { QQmlEngine engine; @@ -744,6 +774,15 @@ void tst_QmlCppCodegen::basicBlocksWithBackJump() QVERIFY(!expectingMessage); } +void tst_QmlCppCodegen::basicBlocksWithBackJump_infinite() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/basicBlocksWithBackJump_infinite.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> o(component.create()); + QVERIFY(!o.isNull()); +} + void tst_QmlCppCodegen::basicDTZ() { QQmlEngine engine; diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index 2836e41a00..da30307466 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -24,12 +24,14 @@ #include <algorithm> +using namespace Qt::Literals::StringLiterals; + static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments) { allArguments.reserve(arguments.size()); for (const QString &argument : arguments) { // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it - if (argument.startsWith(QLatin1Char('@'))) { + if (argument.startsWith(u'@')) { QString optionsFile = argument; optionsFile.remove(0, 1); if (optionsFile.isEmpty()) { @@ -59,55 +61,43 @@ int main(int argc, char **argv) QHashSeed::setDeterministicGlobalSeed(); QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName(QStringLiteral("qmlcachegen")); + QCoreApplication::setApplicationName("qmlcachegen"_L1); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption bareOption(QStringLiteral("bare"), QCoreApplication::translate("main", "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version.")); + QCommandLineOption bareOption("bare"_L1, QCoreApplication::translate("main", "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version.")); parser.addOption(bareOption); - QCommandLineOption filterResourceFileOption(QStringLiteral("filter-resource-file"), QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); + QCommandLineOption filterResourceFileOption("filter-resource-file"_L1, QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); parser.addOption(filterResourceFileOption); - QCommandLineOption resourceFileMappingOption(QStringLiteral("resource-file-mapping"), QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name=new-name")); + QCommandLineOption resourceFileMappingOption("resource-file-mapping"_L1, QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name=new-name")); parser.addOption(resourceFileMappingOption); - QCommandLineOption resourceOption(QStringLiteral("resource"), QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); + QCommandLineOption resourceOption("resource"_L1, QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); parser.addOption(resourceOption); - QCommandLineOption resourcePathOption(QStringLiteral("resource-path"), QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); + QCommandLineOption resourcePathOption("resource-path"_L1, QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); parser.addOption(resourcePathOption); - QCommandLineOption resourceNameOption(QStringLiteral("resource-name"), - QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), - QCoreApplication::translate("main", "compiled-file-list")); + QCommandLineOption resourceNameOption("resource-name"_L1, QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), QCoreApplication::translate("main", "compiled-file-list")); parser.addOption(resourceNameOption); - QCommandLineOption directCallsOption(QStringLiteral("direct-calls"), QCoreApplication::translate("main", "This option is ignored.")); + QCommandLineOption directCallsOption("direct-calls"_L1, QCoreApplication::translate("main", "This option is ignored.")); directCallsOption.setFlags(QCommandLineOption::HiddenFromHelp); parser.addOption(directCallsOption); - QCommandLineOption importsOption( - QStringLiteral("i"), - QCoreApplication::translate("main", "Import extra qmldir"), - QCoreApplication::translate("main", "qmldir file")); + QCommandLineOption importsOption("i"_L1, QCoreApplication::translate("main", "Import extra qmldir"), QCoreApplication::translate("main", "qmldir file")); parser.addOption(importsOption); - QCommandLineOption importPathOption( - QStringLiteral("I"), - QCoreApplication::translate("main", "Look for QML modules in specified directory"), - QCoreApplication::translate("main", "import directory")); + QCommandLineOption importPathOption("I"_L1, QCoreApplication::translate("main", "Look for QML modules in specified directory"), QCoreApplication::translate("main", "import directory")); parser.addOption(importPathOption); - QCommandLineOption onlyBytecode( - QStringLiteral("only-bytecode"), - QCoreApplication::translate( - "main", "Generate only byte code for bindings and functions, no C++ code")); + QCommandLineOption onlyBytecode("only-bytecode"_L1, QCoreApplication::translate("main", "Generate only byte code for bindings and functions, no C++ code")); parser.addOption(onlyBytecode); - QCommandLineOption verboseOption( - QStringLiteral("verbose"), - QCoreApplication::translate("main", "Output compile warnings")); + QCommandLineOption verboseOption("verbose"_L1, QCoreApplication::translate("main", "Output compile warnings")); parser.addOption(verboseOption); + QCommandLineOption validateBasicBlocksOption("validate-basic-blocks"_L1, QCoreApplication::translate("main", "Performs checks on the basic blocks of a function compiled ahead of time to validate its structure and coherence")); + parser.addOption(validateBasicBlocksOption); - QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); + QCommandLineOption outputFileOption("o"_L1, QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); parser.addOption(outputFileOption); - parser.addPositionalArgument(QStringLiteral("[qml file]"), - QStringLiteral("QML source file to generate cache for.")); + parser.addPositionalArgument("[qml file]"_L1, "QML source file to generate cache for."_L1); parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); @@ -129,9 +119,9 @@ int main(int argc, char **argv) if (parser.isSet(outputFileOption)) outputFileName = parser.value(outputFileOption); - if (outputFileName.endsWith(QLatin1String(".cpp"))) { + if (outputFileName.endsWith(".cpp"_L1)) { target = GenerateCpp; - if (outputFileName.endsWith(QLatin1String("qmlcache_loader.cpp"))) + if (outputFileName.endsWith("qmlcache_loader.cpp"_L1)) target = GenerateLoader; } @@ -142,13 +132,13 @@ int main(int argc, char **argv) if (sources.isEmpty()){ parser.showHelp(); } else if (sources.size() > 1 && (target != GenerateLoader && target != GenerateLoaderStandAlone)) { - fprintf(stderr, "%s\n", qPrintable(QStringLiteral("Too many input files specified: '") + sources.join(QStringLiteral("' '")) + QLatin1Char('\''))); + fprintf(stderr, "%s\n", qPrintable("Too many input files specified: '"_L1 + sources.join("' '"_L1) + u'\'')); return EXIT_FAILURE; } const QString inputFile = !sources.isEmpty() ? sources.first() : QString(); if (outputFileName.isEmpty()) - outputFileName = inputFile + QLatin1Char('c'); + outputFileName = inputFile + u'c'; if (parser.isSet(filterResourceFileOption)) return qRelocateResourceFile(inputFile, outputFileName); @@ -160,7 +150,7 @@ int main(int argc, char **argv) if (!qQmlJSGenerateLoader( mapper.resourcePaths(QQmlJSResourceFileMapper::allQmlJSFilter()), outputFileName, parser.values(resourceFileMappingOption), &error.message)) { - error.augment(QLatin1String("Error generating loader stub: ")).print(); + error.augment("Error generating loader stub: "_L1).print(); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -170,7 +160,7 @@ int main(int argc, char **argv) QQmlJSCompileError error; if (!qQmlJSGenerateLoader(sources, outputFileName, parser.values(resourceNameOption), &error.message)) { - error.augment(QLatin1String("Error generating loader stub: ")).print(); + error.augment("Error generating loader stub: "_L1).print(); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -205,13 +195,12 @@ int main(int argc, char **argv) } if (target == GenerateCpp) { - inputFileUrl = QStringLiteral("qrc://") + inputResourcePath; + inputFileUrl = "qrc://"_L1 + inputResourcePath; saveFunction = [inputResourcePath, outputFileName]( const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString) { - return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions, - errorString); + return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions, errorString); }; } else { @@ -227,20 +216,20 @@ int main(int argc, char **argv) }; } - if (inputFile.endsWith(QLatin1String(".qml"))) { + if (inputFile.endsWith(".qml"_L1)) { QQmlJSCompileError error; if (target != GenerateCpp || inputResourcePath.isEmpty() || parser.isSet(onlyBytecode)) { if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error, /* storeSourceLocation */ false)) { - error.augment(QStringLiteral("Error compiling qml file: ")).print(); + error.augment("Error compiling qml file: "_L1).print(); return EXIT_FAILURE; } } else { QStringList importPaths; if (parser.isSet(resourceOption)) { - importPaths.append(QLatin1String(":/qt-project.org/imports")); - importPaths.append(QLatin1String(":/qt/qml")); + importPaths.append("qt-project.org/imports"_L1); + importPaths.append("qt/qml"_L1); }; if (parser.isSet(importPathOption)) @@ -264,24 +253,27 @@ int main(int argc, char **argv) QQmlJSAotCompiler cppCodeGen( &importer, u':' + inputResourcePath, parser.values(importsOption), &logger); + if (parser.isSet(validateBasicBlocksOption)) + cppCodeGen.m_flags.setFlag(QQmlJSAotCompiler::ValidateBasicBlocks); + if (!qCompileQmlFile(inputFile, saveFunction, &cppCodeGen, &error, /* storeSourceLocation */ true)) { - error.augment(QStringLiteral("Error compiling qml file: ")).print(); + error.augment("Error compiling qml file: "_L1).print(); return EXIT_FAILURE; } QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); if (!warnings.isEmpty()) { - logger.log(QStringLiteral("Type warnings occurred while compiling file:"), + logger.log("Type warnings occurred while compiling file:"_L1, qmlImport, QQmlJS::SourceLocation()); logger.processMessages(warnings, qmlImport); } } - } else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) { + } else if (inputFile.endsWith(".js"_L1) || inputFile.endsWith(".mjs"_L1)) { QQmlJSCompileError error; if (!qCompileJSFile(inputFile, inputFileUrl, saveFunction, &error)) { - error.augment(QLatin1String("Error compiling js file: ")).print(); + error.augment("Error compiling js file: "_L1).print(); return EXIT_FAILURE; } } else { |