diff options
19 files changed, 134 insertions, 59 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index bf05c5c538..8ec730a33d 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -315,6 +315,7 @@ void Codegen::accept(Node *node) void Codegen::statement(Statement *ast) { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); RegisterScope scope(this); bytecodeGenerator->setLocation(ast->firstSourceLocation()); @@ -327,11 +328,12 @@ void Codegen::statement(Statement *ast) void Codegen::statement(ExpressionNode *ast) { - RegisterScope scope(this); - if (! ast) { return; } else { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + RegisterScope scope(this); + Result r(nx); qSwap(_expr, r); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); @@ -358,6 +360,7 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift if (!ast) return; + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); Result r(iftrue, iffalse, trueBlockFollowsCondition); qSwap(_expr, r); accept(ast); @@ -381,6 +384,7 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift Codegen::Reference Codegen::expression(ExpressionNode *ast) { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); Result r; if (ast) { qSwap(_expr, r); diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 0bc04750f7..289728f505 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -761,6 +761,31 @@ protected: bool _onoff; }; + class RecursionDepthCheck { + public: + RecursionDepthCheck(Codegen *cg, const AST::SourceLocation &loc) + : _cg(cg) + { +#ifdef QT_NO_DEBUG + const int depthLimit = 4000; // limit to ~1000 deep +#else + const int depthLimit = 1000; // limit to ~250 deep +#endif // QT_NO_DEBUG + + ++_cg->_recursionDepth; + if (_cg->_recursionDepth > depthLimit) + _cg->throwSyntaxError(loc, QStringLiteral("Maximum statement or expression depth exceeded")); + } + + ~RecursionDepthCheck() + { --_cg->_recursionDepth; } + + private: + Codegen *_cg; + }; + int _recursionDepth = 0; + friend class RecursionDepthCheck; + private: VolatileMemoryLocations scanVolatileMemoryLocations(AST::Node *ast) const; void handleConstruct(const Reference &base, AST::ArgumentList *args); diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 2026e64929..fc3ac769ae 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -96,6 +96,25 @@ void ScanFunctions::leaveEnvironment() _context = _contextStack.isEmpty() ? nullptr : _contextStack.top(); } +bool ScanFunctions::preVisit(Node *ast) +{ + if (_cg->hasError) + return false; + ++_recursionDepth; + + if (_recursionDepth > 1000) { + _cg->throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Maximum statement or expression depth exceeded")); + return false; + } + + return true; +} + +void ScanFunctions::postVisit(Node *) +{ + --_recursionDepth; +} + void ScanFunctions::checkDirectivePrologue(StatementList *ast) { for (StatementList *it = ast; it; it = it->next) { diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index bb07540ec9..4463a4f4f3 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -96,6 +96,9 @@ protected: using Visitor::visit; using Visitor::endVisit; + bool preVisit(AST::Node *ast) override; + void postVisit(AST::Node *) override; + void checkDirectivePrologue(AST::StatementList *ast); void checkName(const QStringRef &name, const AST::SourceLocation &loc); @@ -169,6 +172,8 @@ protected: bool _allowFuncDecls; ContextType defaultProgramType; + unsigned _recursionDepth = 0; + private: static constexpr AST::Node *astNodeForGlobalEnvironment = nullptr; }; diff --git a/src/qml/jsruntime/qv4include.cpp b/src/qml/jsruntime/qv4include.cpp index e456879d9c..36569b0a60 100644 --- a/src/qml/jsruntime/qv4include.cpp +++ b/src/qml/jsruntime/qv4include.cpp @@ -166,7 +166,7 @@ void QV4Include::finished() QmlIR::Document::removeScriptPragmas(code); QV4::Scoped<QV4::QmlContext> qml(scope, m_qmlContext.value()); - QV4::Script script(v4, qml, code, m_url.toString()); + QV4::Script script(v4, qml, /*parse as QML binding*/false, code, m_url.toString()); script.parse(); if (!scope.engine->hasException) diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index 951675b468..f80db86be5 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -240,23 +240,7 @@ Script *Script::createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlCo QString sourceCode = QString::fromUtf8(data); QmlIR::Document::removeScriptPragmas(sourceCode); - auto result = new QV4::Script(engine, qmlContext, sourceCode, originalUrl.toString()); + auto result = new QV4::Script(engine, qmlContext, /*parseAsBinding*/false, sourceCode, originalUrl.toString()); result->parse(); return result; } - -QV4::ReturnedValue Script::evaluate(ExecutionEngine *engine, const QString &script, QmlContext *qmlContext) -{ - QV4::Scope scope(engine); - QV4::Script qmlScript(engine, qmlContext, script, QString()); - - qmlScript.parse(); - QV4::ScopedValue result(scope); - if (!scope.engine->hasException) - result = qmlScript.run(); - if (scope.engine->hasException) { - scope.engine->catchException(); - return Encode::undefined(); - } - return result->asReturnedValue(); -} diff --git a/src/qml/jsruntime/qv4script_p.h b/src/qml/jsruntime/qv4script_p.h index c138e4a538..a1e9b83a8b 100644 --- a/src/qml/jsruntime/qv4script_p.h +++ b/src/qml/jsruntime/qv4script_p.h @@ -73,10 +73,10 @@ struct Q_QML_EXPORT Script { : sourceFile(source), line(line), column(column), sourceCode(sourceCode) , context(scope), strictMode(false), inheritContext(false), parsed(false), contextType(mode) , vmFunction(nullptr), parseAsBinding(false) {} - Script(ExecutionEngine *engine, QmlContext *qml, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) + Script(ExecutionEngine *engine, QmlContext *qml, bool parseAsBinding, const QString &sourceCode, const QString &source = QString(), int line = 1, int column = 0) : sourceFile(source), line(line), column(column), sourceCode(sourceCode) , context(engine->rootContext()), strictMode(false), inheritContext(true), parsed(false) - , vmFunction(nullptr), parseAsBinding(true) { + , vmFunction(nullptr), parseAsBinding(parseAsBinding) { if (qml) qmlContext.set(engine, *qml); } @@ -106,8 +106,6 @@ struct Q_QML_EXPORT Script { QList<QQmlError> *reportedErrors = nullptr, QV4::Compiler::ContextType contextType = QV4::Compiler::ContextType::Global); static Script *createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error); - - static ReturnedValue evaluate(ExecutionEngine *engine, const QString &script, QmlContext *qmlContext); }; } diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 6549e5bfa3..860a4e999e 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -614,8 +614,16 @@ bool Parser::parse(int startToken) program = 0; do { - if (++tos == stack_size) + if (++tos == stack_size) { reallocateStack(); + if (stack_size > 10000) { + // We're now in some serious right-recursive stuff, which will probably result in + // an AST that's so deep that recursively visiting it will run out of stack space. + const QString msg = QCoreApplication::translate("QQmlParser", "Maximum statement or expression depth exceeded"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + return false; + } + } state_stack[tos] = action; diff --git a/src/qml/parser/qqmljsmemorypool_p.h b/src/qml/parser/qqmljsmemorypool_p.h index 9a480f1224..afd0f809da 100644 --- a/src/qml/parser/qqmljsmemorypool_p.h +++ b/src/qml/parser/qqmljsmemorypool_p.h @@ -88,7 +88,7 @@ public: inline void *allocate(size_t size) { - size = (size + 7) & ~7; + size = (size + 7) & ~size_t(7); if (Q_LIKELY(_ptr && (_ptr + size < _end))) { void *addr = _ptr; _ptr += size; @@ -113,7 +113,9 @@ public: private: Q_NEVER_INLINE void *allocate_helper(size_t size) { - Q_ASSERT(size < BLOCK_SIZE); + size_t currentBlockSize = DEFAULT_BLOCK_SIZE; + while (Q_UNLIKELY(size >= currentBlockSize)) + currentBlockSize *= 2; if (++_blockCount == _allocatedBlocks) { if (! _allocatedBlocks) @@ -121,7 +123,7 @@ private: else _allocatedBlocks *= 2; - _blocks = (char **) realloc(_blocks, sizeof(char *) * _allocatedBlocks); + _blocks = reinterpret_cast<char **>(realloc(_blocks, sizeof(char *) * size_t(_allocatedBlocks))); Q_CHECK_PTR(_blocks); for (int index = _blockCount; index < _allocatedBlocks; ++index) @@ -131,12 +133,12 @@ private: char *&block = _blocks[_blockCount]; if (! block) { - block = (char *) malloc(BLOCK_SIZE); + block = reinterpret_cast<char *>(malloc(currentBlockSize)); Q_CHECK_PTR(block); } _ptr = block; - _end = _ptr + BLOCK_SIZE; + _end = _ptr + currentBlockSize; void *addr = _ptr; _ptr += size; @@ -153,7 +155,7 @@ private: enum { - BLOCK_SIZE = 8 * 1024, + DEFAULT_BLOCK_SIZE = 8 * 1024, DEFAULT_BLOCK_COUNT = 8 }; }; diff --git a/src/qml/qml/qqmljavascriptexpression.cpp b/src/qml/qml/qqmljavascriptexpression.cpp index 9f2a96d5d9..380163202a 100644 --- a/src/qml/qml/qqmljavascriptexpression.cpp +++ b/src/qml/qml/qqmljavascriptexpression.cpp @@ -408,7 +408,7 @@ QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scopeObje QV4::Scope scope(v4); QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxt, scopeObject)); - QV4::Script script(v4, qmlContext, code, filename, line); + QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line); QV4::ScopedValue result(scope); script.parse(); if (!v4->hasException) @@ -438,7 +438,7 @@ void QQmlJavaScriptExpression::createQmlBinding(QQmlContextData *ctxt, QObject * QV4::Scope scope(v4); QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxt, qmlScope)); - QV4::Script script(v4, qmlContext, code, filename, line); + QV4::Script script(v4, qmlContext, /*parse as QML binding*/true, code, filename, line); script.parse(); if (v4->hasException) { QQmlDelayedError *error = delayedError(); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index cb90af4cf0..9df502f778 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -3285,14 +3285,7 @@ QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const if (hasInlineSourceCode) return QDateTime(); - QDateTime timeStamp = fileInfo.lastModified(); - if (timeStamp.isValid()) - return timeStamp; - - static QDateTime appTimeStamp; - if (!appTimeStamp.isValid()) - appTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified(); - return appTimeStamp; + return fileInfo.lastModified(); } bool QQmlDataBlob::SourceCodeData::exists() const diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index e7e19b041e..77ed8a659c 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -2403,7 +2403,11 @@ void QQuickPathViewPrivate::snapToIndex(int index, MovementReason reason) const int duration = highlightMoveDuration; - if (!duration) { + const qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount); + const qreal averageItemLength = path->path().length() / count; + const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate + + if (!duration || qAbs(offset - targetOffset) < threshold || (qFuzzyIsNull(targetOffset) && qAbs(modelCount - offset) < threshold)) { tl.set(moveOffset, targetOffset); } else if (moveDirection == QQuickPathView::Positive || (moveDirection == QQuickPathView::Shortest && targetOffset - offset > modelCount/2.0)) { qreal distance = modelCount - targetOffset + offset; diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index de1169c972..b1900c5b7a 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1647,8 +1647,9 @@ void QQuickTableViewPrivate::connectToModel() QObjectPrivate::connect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback); if (tableModel) { - QObjectPrivate::connect(tableModel, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); - QObjectPrivate::connect(tableModel, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + const auto tm = tableModel.data(); + QObjectPrivate::connect(tm, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); + QObjectPrivate::connect(tm, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); } if (auto const aim = model->abstractItemModel()) { @@ -1678,8 +1679,9 @@ void QQuickTableViewPrivate::disconnectFromModel() QObjectPrivate::disconnect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback); if (tableModel) { - QObjectPrivate::disconnect(tableModel, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); - QObjectPrivate::disconnect(tableModel, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + const auto tm = tableModel.data(); + QObjectPrivate::disconnect(tm, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); + QObjectPrivate::disconnect(tm, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); } if (auto const aim = model->abstractItemModel()) { diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp index 74426c5c4d..405e2ab100 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp @@ -99,13 +99,6 @@ void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargin xTarget.resize(columns + 1); yTarget.resize(rows + 1); - bool oldAA = painter->testRenderHint(QPainter::Antialiasing); - if (painter->paintEngine()->type() != QPaintEngine::OpenGL - && painter->paintEngine()->type() != QPaintEngine::OpenGL2 - && oldAA && painter->combinedTransform().type() != QTransform::TxNone) { - painter->setRenderHint(QPainter::Antialiasing, false); - } - xTarget[0] = targetRect.left(); xTarget[1] = targetCenterLeft; xTarget[columns - 1] = targetCenterRight; @@ -311,9 +304,6 @@ void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargin painter->drawPixmapFragments(opaqueData.data(), opaqueData.size(), pixmap, QPainter::OpaqueHint); if (translucentData.size()) painter->drawPixmapFragments(translucentData.data(), translucentData.size(), pixmap); - - if (oldAA) - painter->setRenderHint(QPainter::Antialiasing, true); } } // QSGSoftwareHelpers namespace @@ -464,6 +454,8 @@ static Qt::TileRule getTileRule(qreal factor) void QSGSoftwareInternalImageNode::paint(QPainter *painter) { painter->setRenderHint(QPainter::SmoothPixmapTransform, m_smooth); + // Disable antialiased clipping. It causes transformed tiles to have gaps. + painter->setRenderHint(QPainter::Antialiasing, false); const QPixmap &pm = m_mirror || m_textureIsLayer ? m_cachedMirroredPixmap : pixmap(); @@ -494,6 +486,7 @@ void QSGSoftwareInternalImageNode::paint(QPainter *painter) } } + QRectF QSGSoftwareInternalImageNode::rect() const { return m_targetRect; diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp index 2c361e03e2..f50fa00b0b 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp @@ -121,7 +121,7 @@ void QSGSoftwareInternalRectangleNode::setGradientStops(const QGradientStops &st for (const QGradientStop &stop : qAsConst(stops)) { if (stop.first < 0.0 || stop.first > 1.0) { needsNormalization = true; - continue; + break; } } @@ -425,8 +425,11 @@ void QSGSoftwareInternalRectangleNode::generateCornerPixmap() { //Generate new corner Pixmap int radius = qFloor(qMin(qMin(m_rect.width(), m_rect.height()) * 0.5, m_radius)); + const auto width = qRound(radius * 2 * m_devicePixelRatio); + + if (m_cornerPixmap.width() != width) + m_cornerPixmap = QPixmap(width, width); - m_cornerPixmap = QPixmap(qRound(radius * 2 * m_devicePixelRatio), qRound(radius * 2 * m_devicePixelRatio)); m_cornerPixmap.setDevicePixelRatio(m_devicePixelRatio); m_cornerPixmap.fill(Qt::transparent); diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes.cpp index 471624d3f8..bd0698be6c 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarepublicnodes.cpp @@ -99,6 +99,8 @@ void QSGSoftwareImageNode::paint(QPainter *painter) updateCachedMirroredPixmap(); painter->setRenderHint(QPainter::SmoothPixmapTransform, (m_filtering == QSGTexture::Linear)); + // Disable antialiased clipping. It causes transformed tiles to have gaps. + painter->setRenderHint(QPainter::Antialiasing, false); if (!m_cachedPixmap.isNull()) { painter->drawPixmap(m_rect, m_cachedPixmap, m_sourceRect); @@ -194,6 +196,9 @@ void QSGSoftwareNinePatchNode::update() void QSGSoftwareNinePatchNode::paint(QPainter *painter) { + // Disable antialiased clipping. It causes transformed tiles to have gaps. + painter->setRenderHint(QPainter::Antialiasing, false); + if (m_margins.isNull()) painter->drawPixmap(m_bounds, m_pixmap, QRectF(0, 0, m_pixmap.width(), m_pixmap.height())); else diff --git a/tests/auto/qml/qqmlecmascript/data/js/include2.js b/tests/auto/qml/qqmlecmascript/data/js/include2.js index 2a0c039dfa..7cfcdd95e2 100644 --- a/tests/auto/qml/qqmlecmascript/data/js/include2.js +++ b/tests/auto/qml/qqmlecmascript/data/js/include2.js @@ -2,3 +2,8 @@ test2 = true var test2_1 = true Qt.include("include3.js"); + +function withTokensAllowedInJSButKeywordsInQML(char) +{ + var double; +} diff --git a/tests/auto/qml/v4misc/tst_v4misc.cpp b/tests/auto/qml/v4misc/tst_v4misc.cpp index ecc3a4100c..2412ca7f92 100644 --- a/tests/auto/qml/v4misc/tst_v4misc.cpp +++ b/tests/auto/qml/v4misc/tst_v4misc.cpp @@ -43,6 +43,8 @@ private slots: void subClassing_data(); void subClassing(); + + void nestingDepth(); }; void tst_v4misc::tdzOptimizations_data() @@ -59,7 +61,7 @@ void tst_v4misc::tdzOptimizations() QFETCH(QString, scriptToCompile); QV4::ExecutionEngine v4; - QV4::Script script(&v4, nullptr, scriptToCompile); + QV4::Script script(&v4, nullptr, /*parse as binding*/false, scriptToCompile); script.parse(); QVERIFY(!v4.hasException); @@ -173,6 +175,28 @@ void tst_v4misc::subClassing() QVERIFY(!result.isError()); } +void tst_v4misc::nestingDepth() +{ + { // left recursive + QString s(40000, '`'); + + QJSEngine engine; + QJSValue result = engine.evaluate(s); + QVERIFY(result.isError()); + QCOMPARE(result.toString(), "SyntaxError: Maximum statement or expression depth exceeded"); + } + + { // right recursive + QString s(200000, '-'); + s += "\nd"; + + QJSEngine engine; + QJSValue result = engine.evaluate(s); + QVERIFY(result.isError()); + QCOMPARE(result.toString(), "SyntaxError: Maximum statement or expression depth exceeded"); + } +} + QTEST_MAIN(tst_v4misc); #include "tst_v4misc.moc" diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro index 9662690395..bee0b9a37e 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -24,6 +24,7 @@ contains(CMAKE_BIN_DIR, "^\\.\\./.*") { load(qt_build_paths) +equals(QMAKE_HOST.os, Windows): CMAKE_BIN_SUFFIX = ".exe" cmake_config_file.input = $$PWD/Qt5QuickCompilerConfig.cmake.in cmake_config_file.output = $$MODULE_BASE_OUTDIR/lib/cmake/Qt5QuickCompiler/Qt5QuickCompilerConfig.cmake QMAKE_SUBSTITUTES += cmake_config_file |