aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier De Cannière <olivier.decanniere@qt.io>2023-07-11 15:01:10 +0200
committerOlivier De Cannière <olivier.decanniere@qt.io>2023-08-04 09:34:09 +0200
commit2cf9aeccddbd06a66df94bd27916714c4a5c7e24 (patch)
treef147cabe8e7f8d3dfcf1b02cf8d730e851fcc0a6
parent0667e68e7715f60916ee91189925855e1dcf5f0e (diff)
Compiler: Separate function prolog block and add validation of blocks
The function prolog logic is now separated in its own basic block. The first "real" block with user code starts at offset 0. Having the function prolog as a hidden part of the first block caused some inconsistencies in block generation and would create empty blocks. This happened for example when a back edge of a loop would target offset 0 in code where a loop condition is the very first set of instructions that are run. This is because the target block offset didn't exist due to it being part of the hidden prolog block. Validation for the basic blocks was also added. This checks for three things at the moment: 1. That return and throw blocks don't have jump targets. 2. That the basic blocks graph is connected. 3. That jump targets are the first offset of a block. Test tst_QmlCppCodegen::basicBlocksWithBackJump_infinite() is expected to fail because it contains an infinite loop and the basic blocks that are generated for it are inconsistent due to dead-code elimination happening earlier in compilation. Debug outputs for dumping basic blocks were also adapted to reflect these changes. Change-Id: I513f73856412d488d443c2b47a052b0023d45496 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-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 {