aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/doc/src/javascript/finetuning.qdoc5
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks.cpp143
-rw-r--r--src/qmlcompiler/qqmljsbasicblocks_p.h19
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp12
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h2
-rw-r--r--src/qmlcompiler/qqmljscompiler.cpp6
-rw-r--r--src/qmlcompiler/qqmljscompiler_p.h9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/basicBlocksWithBackJump_infinite.qml13
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp39
-rw-r--r--tools/qmlcachegen/qmlcachegen.cpp84
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()
" &#160; 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, "&#160;"_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 {