diff --git a/qmake/library/proitems.h b/qmake/library/proitems.h index 05d9e8da28..c81e205699 100644 --- a/qmake/library/proitems.h +++ b/qmake/library/proitems.h @@ -329,6 +329,9 @@ enum ProToken { // - function name: hash (2), length (1), chars (length) // - body length (2) // - body + TokTerminator (body length) + TokBypassNesting, // escape from function local variable scopes: + // - block length (2) + // - block + TokTerminator (block length) TokMask = 0xff, TokQuoted = 0x100, // The expression is quoted => join expanded stringlist TokNewStr = 0x200 // Next stringlist element diff --git a/qmake/library/qmakeevaluator.cpp b/qmake/library/qmakeevaluator.cpp index cc57aa7f2b..4ca87e8bc7 100644 --- a/qmake/library/qmakeevaluator.cpp +++ b/qmake/library/qmakeevaluator.cpp @@ -594,6 +594,24 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock( tokPtr += blockLen; okey = true, or_op = false; // force next evaluation break; + case TokBypassNesting: + blockLen = getBlockLen(tokPtr); + if ((m_cumulative || okey != or_op) && blockLen) { + ProValueMapStack savedValuemapStack = m_valuemapStack; + m_valuemapStack.clear(); + m_valuemapStack.append(savedValuemapStack.takeFirst()); + traceMsg("visiting nesting-bypassing block"); + ret = visitProBlock(tokPtr); + traceMsg("visited nesting-bypassing block"); + savedValuemapStack.prepend(m_valuemapStack.first()); + m_valuemapStack = savedValuemapStack; + } else { + traceMsg("skipped nesting-bypassing block"); + ret = ReturnTrue; + } + tokPtr += blockLen; + okey = true, or_op = false; // force next evaluation + break; case TokTestDef: case TokReplaceDef: if (m_cumulative || okey != or_op) { diff --git a/qmake/library/qmakeparser.cpp b/qmake/library/qmakeparser.cpp index 56b217dfbb..78350c76c4 100644 --- a/qmake/library/qmakeparser.cpp +++ b/qmake/library/qmakeparser.cpp @@ -118,6 +118,7 @@ static struct { QString strfor; QString strdefineTest; QString strdefineReplace; + QString strbypassNesting; QString stroption; QString strreturn; QString strnext; @@ -141,6 +142,7 @@ void QMakeParser::initialize() statics.strfor = QLatin1String("for"); statics.strdefineTest = QLatin1String("defineTest"); statics.strdefineReplace = QLatin1String("defineReplace"); + statics.strbypassNesting = QLatin1String("bypassNesting"); statics.stroption = QLatin1String("option"); statics.strreturn = QLatin1String("return"); statics.strnext = QLatin1String("next"); @@ -1157,6 +1159,25 @@ void QMakeParser::finalizeCall(ushort *&tokPtr, ushort *uc, ushort *ptr, int arg } parseError(fL1S("%1(function) requires one literal argument.").arg(*defName)); return; + } else if (m_tmp == statics.strbypassNesting) { + if (*uce != TokFuncTerminator) { + bogusTest(tokPtr, fL1S("%1() requires zero arguments.").arg(m_tmp)); + return; + } + if (!(m_blockstack.top().nest & NestFunction)) { + bogusTest(tokPtr, fL1S("Unexpected %1().").arg(m_tmp)); + return; + } + if (m_invert) { + bogusTest(tokPtr, fL1S("Unexpected NOT operator in front of %1().").arg(m_tmp)); + return; + } + flushScopes(tokPtr); + putLineMarker(tokPtr); + putOperator(tokPtr); + putTok(tokPtr, TokBypassNesting); + enterScope(tokPtr, true, StCtrl); + return; } else if (m_tmp == statics.strreturn) { if (m_blockstack.top().nest & NestFunction) { if (argc > 1) { @@ -1425,7 +1446,7 @@ static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outS "TokReturn", "TokBreak", "TokNext", "TokNot", "TokAnd", "TokOr", "TokBranch", "TokForLoop", - "TokTestDef", "TokReplaceDef" + "TokTestDef", "TokReplaceDef", "TokBypassNesting" }; while (offset != limit) { @@ -1509,6 +1530,9 @@ static bool getBlock(const ushort *tokens, int limit, int &offset, QString *outS if (ok) ok = getSubBlock(tokens, limit, offset, outStr, indent, "body"); break; + case TokBypassNesting: + ok = getSubBlock(tokens, limit, offset, outStr, indent, "block"); + break; default: Q_ASSERT(!"unhandled token"); } diff --git a/tests/auto/tools/qmakelib/evaltest.cpp b/tests/auto/tools/qmakelib/evaltest.cpp index e3be294e5f..4e215b8570 100644 --- a/tests/auto/tools/qmakelib/evaltest.cpp +++ b/tests/auto/tools/qmakelib/evaltest.cpp @@ -633,6 +633,31 @@ void tst_qmakelib::addControlStructs() << "" << true; + QTest::newRow("bypassNesting()") + << "defineTest(func) {\n" + "LOCAL = 1\n" + "bypassNesting() {\n" + "OUT = 1\n" + "!isEmpty(GLOBAL): OUT1 = 1\n" + "!isEmpty(LOCAL): OUT2 = 1\n" + "}\n" + "}\n" + "GLOBAL = 1\n" + "func()" + << "GLOBAL = 1\nLOCAL = UNDEF\nOUT = 1\nOUT1 = 1\nOUT2 = UNDEF" + << "" + << true; + + QTest::newRow("error() from bypassNesting()") + << "defineTest(func) {\n" + "bypassNesting() { error(error) }\n" + "}\n" + "func()\n" + "OKE = 1" + << "OKE = UNDEF" + << "Project ERROR: error" + << false; + QTest::newRow("top-level return()") << "VAR = good\nreturn()\nVAR = bad" << "VAR = good" diff --git a/tests/auto/tools/qmakelib/parsertest.cpp b/tests/auto/tools/qmakelib/parsertest.cpp index dc92f98f45..70f1be5fc3 100644 --- a/tests/auto/tools/qmakelib/parsertest.cpp +++ b/tests/auto/tools/qmakelib/parsertest.cpp @@ -1684,6 +1684,57 @@ void tst_qmakelib::addParseCustomFunctions() /* 22 */ << H(TokTerminator)) << "" << true; + + QTest::newRow("bypassNesting()-{return}") + << "defineTest(test) { bypassNesting() { return(true) } }" + << TS( + /* 0 */ << H(TokLine) << H(1) + /* 2 */ << H(TokTestDef) << HS(L"test") + /* 10 */ /* body */ << I(16) + /* 12 */ << H(TokLine) << H(1) + /* 14 */ << H(TokBypassNesting) + /* 15 */ /* block */ << I(10) + /* 17 */ << H(TokLine) << H(1) + /* 19 */ << H(TokLiteral | TokNewStr) << S(L"true") + /* 25 */ << H(TokReturn) + /* 26 */ << H(TokTerminator) + /* 27 */ << H(TokTerminator)) + << "" + << true; + + QTest::newRow("test-AND-bypassNesting()-{}") + << "defineTest(test) { test: bypassNesting() {} }" + << TS( + /* 0 */ << H(TokLine) << H(1) + /* 2 */ << H(TokTestDef) << HS(L"test") + /* 10 */ /* body */ << I(17) + /* 12 */ << H(TokLine) << H(1) + /* 14 */ << H(TokHashLiteral) << HS(L"test") + /* 22 */ << H(TokCondition) + /* 23 */ << H(TokAnd) + /* 24 */ << H(TokBypassNesting) + /* 25 */ /* block */ << I(1) + /* 27 */ << H(TokTerminator) + /* 28 */ << H(TokTerminator)) + << "" + << true; + + QTest::newRow("test-OR-bypassNesting()-{}") + << "defineTest(test) { test| bypassNesting() {} }" + << TS( + /* 0 */ << H(TokLine) << H(1) + /* 2 */ << H(TokTestDef) << HS(L"test") + /* 10 */ /* body */ << I(17) + /* 12 */ << H(TokLine) << H(1) + /* 14 */ << H(TokHashLiteral) << HS(L"test") + /* 22 */ << H(TokCondition) + /* 23 */ << H(TokOr) + /* 24 */ << H(TokBypassNesting) + /* 25 */ /* block */ << I(1) + /* 27 */ << H(TokTerminator) + /* 28 */ << H(TokTerminator)) + << "" + << true; } void tst_qmakelib::addParseAbuse() @@ -1736,6 +1787,24 @@ void tst_qmakelib::addParseAbuse() << "in:1: Unexpected NOT operator in front of function definition." << false; + QTest::newRow("outer-bypassNesting()-{}") + << "bypassNesting() {}" + << TS() + << "in:1: Unexpected bypassNesting()." + << false; + + QTest::newRow("bypassNesting(arg)-{}") + << "defineTest(test) { bypassNesting(arg) {} }" + << TS() + << "in:1: bypassNesting() requires zero arguments." + << false; + + QTest::newRow("NOT-bypassNesting()-{}") + << "defineTest(test) { !bypassNesting() {} }" + << TS() + << "in:1: Unexpected NOT operator in front of bypassNesting()." + << false; + QTest::newRow("AND-test") << ":test" << TS(