make error() propagate from custom replace functions

it didn't, which is rather unexpected.

Change-Id: I8cdb7b1490a8c2207809812b93cc65fbe23a1b98
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Oswald Buddenhagen 2016-06-29 16:56:57 +02:00
parent b27d4835c2
commit 339b9706cc
4 changed files with 144 additions and 55 deletions

View File

@ -409,7 +409,7 @@ static ALWAYS_INLINE void addStrList(
} }
} }
void QMakeEvaluator::evaluateExpression( QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpression(
const ushort *&tokPtr, ProStringList *ret, bool joined) const ushort *&tokPtr, ProStringList *ret, bool joined)
{ {
debugMsg(2, joined ? "evaluating joined expression" : "evaluating expression"); debugMsg(2, joined ? "evaluating joined expression" : "evaluating expression");
@ -459,12 +459,15 @@ void QMakeEvaluator::evaluateExpression(
case TokFuncName: { case TokFuncName: {
const ProKey &func = pro->getHashStr(tokPtr); const ProKey &func = pro->getHashStr(tokPtr);
debugMsg(2, "function %s", dbgKey(func)); debugMsg(2, "function %s", dbgKey(func));
addStrList(evaluateExpandFunction(func, tokPtr), tok, ret, pending, joined); ProStringList val;
if (evaluateExpandFunction(func, tokPtr, &val) == ReturnError)
return ReturnError;
addStrList(val, tok, ret, pending, joined);
break; } break; }
default: default:
debugMsg(2, "evaluated expression => %s", dbgStrList(*ret)); debugMsg(2, "evaluated expression => %s", dbgStrList(*ret));
tokPtr--; tokPtr--;
return; return ReturnTrue;
} }
} }
} }
@ -536,7 +539,9 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
case TokAppendUnique: case TokAppendUnique:
case TokRemove: case TokRemove:
case TokReplace: case TokReplace:
visitProVariable(tok, curr, tokPtr); ret = visitProVariable(tok, curr, tokPtr);
if (ret == ReturnError)
break;
curr.clear(); curr.clear();
continue; continue;
case TokBranch: case TokBranch:
@ -696,9 +701,9 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(
continue; continue;
default: { default: {
const ushort *oTokPtr = --tokPtr; const ushort *oTokPtr = --tokPtr;
evaluateExpression(tokPtr, &curr, false); ret = evaluateExpression(tokPtr, &curr, false);
if (tokPtr != oTokPtr) if (ret == ReturnError || tokPtr != oTokPtr)
continue; break;
} }
Q_ASSERT_X(false, "visitProBlock", "unexpected item type"); Q_ASSERT_X(false, "visitProBlock", "unexpected item type");
continue; continue;
@ -731,7 +736,10 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
int index = 0; int index = 0;
ProKey variable; ProKey variable;
ProStringList oldVarVal; ProStringList oldVarVal;
ProString it_list = expandVariableReferences(exprPtr, 0, true).at(0); ProStringList it_list_out;
if (expandVariableReferences(exprPtr, 0, &it_list_out, true) == ReturnError)
return ReturnError;
ProString it_list = it_list_out.at(0);
if (_variable.isEmpty()) { if (_variable.isEmpty()) {
if (it_list != statics.strever) { if (it_list != statics.strever) {
evalError(fL1S("Invalid loop expression.")); evalError(fL1S("Invalid loop expression."));
@ -828,7 +836,7 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProLoop(
return ret; return ret;
} }
void QMakeEvaluator::visitProVariable( QMakeEvaluator::VisitReturn QMakeEvaluator::visitProVariable(
ushort tok, const ProStringList &curr, const ushort *&tokPtr) ushort tok, const ProStringList &curr, const ushort *&tokPtr)
{ {
int sizeHint = *tokPtr++; int sizeHint = *tokPtr++;
@ -837,24 +845,26 @@ void QMakeEvaluator::visitProVariable(
skipExpression(tokPtr); skipExpression(tokPtr);
if (!m_cumulative || !curr.isEmpty()) if (!m_cumulative || !curr.isEmpty())
evalError(fL1S("Left hand side of assignment must expand to exactly one word.")); evalError(fL1S("Left hand side of assignment must expand to exactly one word."));
return; return ReturnTrue;
} }
const ProKey &varName = map(curr.first()); const ProKey &varName = map(curr.first());
if (tok == TokReplace) { // ~= if (tok == TokReplace) { // ~=
// DEFINES ~= s/a/b/?[gqi] // DEFINES ~= s/a/b/?[gqi]
const ProStringList &varVal = expandVariableReferences(tokPtr, sizeHint, true); ProStringList varVal;
if (expandVariableReferences(tokPtr, sizeHint, &varVal, true) == ReturnError)
return ReturnError;
const QString &val = varVal.at(0).toQString(m_tmp1); const QString &val = varVal.at(0).toQString(m_tmp1);
if (val.length() < 4 || val.at(0) != QLatin1Char('s')) { if (val.length() < 4 || val.at(0) != QLatin1Char('s')) {
evalError(fL1S("The ~= operator can handle only the s/// function.")); evalError(fL1S("The ~= operator can handle only the s/// function."));
return; return ReturnTrue;
} }
QChar sep = val.at(1); QChar sep = val.at(1);
QStringList func = val.split(sep); QStringList func = val.split(sep);
if (func.count() < 3 || func.count() > 4) { if (func.count() < 3 || func.count() > 4) {
evalError(fL1S("The s/// function expects 3 or 4 arguments.")); evalError(fL1S("The s/// function expects 3 or 4 arguments."));
return; return ReturnTrue;
} }
bool global = false, quote = false, case_sense = false; bool global = false, quote = false, case_sense = false;
@ -875,7 +885,9 @@ void QMakeEvaluator::visitProVariable(
replaceInList(&valuesRef(varName), regexp, replace, global, m_tmp2); replaceInList(&valuesRef(varName), regexp, replace, global, m_tmp2);
debugMsg(2, "replaced %s with %s", dbgQStr(pattern), dbgQStr(replace)); debugMsg(2, "replaced %s with %s", dbgQStr(pattern), dbgQStr(replace));
} else { } else {
ProStringList varVal = expandVariableReferences(tokPtr, sizeHint); ProStringList varVal;
if (expandVariableReferences(tokPtr, sizeHint, &varVal, false) == ReturnError)
return ReturnError;
switch (tok) { switch (tok) {
default: // whatever - cannot happen default: // whatever - cannot happen
case TokAssign: // = case TokAssign: // =
@ -923,6 +935,8 @@ void QMakeEvaluator::visitProVariable(
else if (varName == statics.strREQUIRES) else if (varName == statics.strREQUIRES)
checkRequirements(values(varName)); checkRequirements(values(varName));
#endif #endif
return ReturnTrue;
} }
void QMakeEvaluator::setTemplate() void QMakeEvaluator::setTemplate()
@ -1610,18 +1624,18 @@ bool QMakeEvaluator::isActiveConfig(const QString &config, bool regex)
return false; return false;
} }
ProStringList QMakeEvaluator::expandVariableReferences( QMakeEvaluator::VisitReturn QMakeEvaluator::expandVariableReferences(
const ushort *&tokPtr, int sizeHint, bool joined) const ushort *&tokPtr, int sizeHint, ProStringList *ret, bool joined)
{ {
ProStringList ret; ret->reserve(sizeHint);
ret.reserve(sizeHint);
forever { forever {
evaluateExpression(tokPtr, &ret, joined); if (evaluateExpression(tokPtr, ret, joined) == ReturnError)
return ReturnError;
switch (*tokPtr) { switch (*tokPtr) {
case TokValueTerminator: case TokValueTerminator:
case TokFuncTerminator: case TokFuncTerminator:
tokPtr++; tokPtr++;
return ret; return ReturnTrue;
case TokArgSeparator: case TokArgSeparator:
if (joined) { if (joined) {
tokPtr++; tokPtr++;
@ -1635,28 +1649,28 @@ ProStringList QMakeEvaluator::expandVariableReferences(
} }
} }
QList<ProStringList> QMakeEvaluator::prepareFunctionArgs(const ushort *&tokPtr) QMakeEvaluator::VisitReturn QMakeEvaluator::prepareFunctionArgs(
const ushort *&tokPtr, QList<ProStringList> *ret)
{ {
QList<ProStringList> args_list;
if (*tokPtr != TokFuncTerminator) { if (*tokPtr != TokFuncTerminator) {
for (;; tokPtr++) { for (;; tokPtr++) {
ProStringList arg; ProStringList arg;
evaluateExpression(tokPtr, &arg, false); if (evaluateExpression(tokPtr, &arg, false) == ReturnError)
args_list << arg; return ReturnError;
*ret << arg;
if (*tokPtr == TokFuncTerminator) if (*tokPtr == TokFuncTerminator)
break; break;
Q_ASSERT(*tokPtr == TokArgSeparator); Q_ASSERT(*tokPtr == TokArgSeparator);
} }
} }
tokPtr++; tokPtr++;
return args_list; return ReturnTrue;
} }
ProStringList QMakeEvaluator::evaluateFunction( QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFunction(
const ProFunctionDef &func, const QList<ProStringList> &argumentsList, VisitReturn *ok) const ProFunctionDef &func, const QList<ProStringList> &argumentsList, ProStringList *ret)
{ {
VisitReturn vr; VisitReturn vr;
ProStringList ret;
if (m_valuemapStack.count() >= 100) { if (m_valuemapStack.count() >= 100) {
evalError(fL1S("Ran into infinite recursion (depth > 100).")); evalError(fL1S("Ran into infinite recursion (depth > 100)."));
@ -1675,25 +1689,22 @@ ProStringList QMakeEvaluator::evaluateFunction(
vr = visitProBlock(func.pro(), func.tokPtr()); vr = visitProBlock(func.pro(), func.tokPtr());
if (vr == ReturnReturn) if (vr == ReturnReturn)
vr = ReturnTrue; vr = ReturnTrue;
ret = m_returnValue; if (vr == ReturnTrue)
*ret = m_returnValue;
m_returnValue.clear(); m_returnValue.clear();
m_current = m_locationStack.pop(); m_current = m_locationStack.pop();
m_valuemapStack.pop(); m_valuemapStack.pop();
} }
if (ok) return vr;
*ok = vr;
if (vr == ReturnTrue)
return ret;
return ProStringList();
} }
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction( QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBoolFunction(
const ProFunctionDef &func, const QList<ProStringList> &argumentsList, const ProFunctionDef &func, const QList<ProStringList> &argumentsList,
const ProString &function) const ProString &function)
{ {
VisitReturn vr; ProStringList ret;
ProStringList ret = evaluateFunction(func, argumentsList, &vr); VisitReturn vr = evaluateFunction(func, argumentsList, &ret);
if (vr == ReturnTrue) { if (vr == ReturnTrue) {
if (ret.isEmpty()) if (ret.isEmpty())
return ReturnTrue; return ReturnTrue;
@ -1721,13 +1732,18 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
{ {
if (int func_t = statics.functions.value(func)) { if (int func_t = statics.functions.value(func)) {
//why don't the builtin functions just use args_list? --Sam //why don't the builtin functions just use args_list? --Sam
return evaluateBuiltinConditional(func_t, func, expandVariableReferences(tokPtr, 5, true)); ProStringList args;
if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
return ReturnError;
return evaluateBuiltinConditional(func_t, func, args);
} }
QHash<ProKey, ProFunctionDef>::ConstIterator it = QHash<ProKey, ProFunctionDef>::ConstIterator it =
m_functionDefs.testFunctions.constFind(func); m_functionDefs.testFunctions.constFind(func);
if (it != m_functionDefs.testFunctions.constEnd()) { if (it != m_functionDefs.testFunctions.constEnd()) {
const QList<ProStringList> args = prepareFunctionArgs(tokPtr); QList<ProStringList> args;
if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
return ReturnError;
traceMsg("calling %s(%s)", dbgKey(func), dbgStrListList(args)); traceMsg("calling %s(%s)", dbgKey(func), dbgStrListList(args));
return evaluateBoolFunction(*it, args, func); return evaluateBoolFunction(*it, args, func);
} }
@ -1737,25 +1753,31 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(
return ReturnFalse; return ReturnFalse;
} }
ProStringList QMakeEvaluator::evaluateExpandFunction( QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction(
const ProKey &func, const ushort *&tokPtr) const ProKey &func, const ushort *&tokPtr, ProStringList *ret)
{ {
if (int func_t = statics.expands.value(func)) { if (int func_t = statics.expands.value(func)) {
//why don't the builtin functions just use args_list? --Sam //why don't the builtin functions just use args_list? --Sam
return evaluateBuiltinExpand(func_t, func, expandVariableReferences(tokPtr, 5, true)); ProStringList args;
if (expandVariableReferences(tokPtr, 5, &args, true) == ReturnError)
return ReturnError;
*ret = evaluateBuiltinExpand(func_t, func, args);
return ReturnTrue;
} }
QHash<ProKey, ProFunctionDef>::ConstIterator it = QHash<ProKey, ProFunctionDef>::ConstIterator it =
m_functionDefs.replaceFunctions.constFind(func); m_functionDefs.replaceFunctions.constFind(func);
if (it != m_functionDefs.replaceFunctions.constEnd()) { if (it != m_functionDefs.replaceFunctions.constEnd()) {
const QList<ProStringList> args = prepareFunctionArgs(tokPtr); QList<ProStringList> args;
if (prepareFunctionArgs(tokPtr, &args) == ReturnError)
return ReturnError;
traceMsg("calling $$%s(%s)", dbgKey(func), dbgStrListList(args)); traceMsg("calling $$%s(%s)", dbgKey(func), dbgStrListList(args));
return evaluateFunction(*it, args, 0); return evaluateFunction(*it, args, ret);
} }
skipExpression(tokPtr); skipExpression(tokPtr);
evalError(fL1S("'%1' is not a recognized replace function.").arg(func.toQString(m_tmp1))); evalError(fL1S("'%1' is not a recognized replace function.").arg(func.toQString(m_tmp1)));
return ProStringList(); return ReturnFalse;
} }
QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional( QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditional(

View File

@ -153,7 +153,7 @@ public:
{ return b ? ReturnTrue : ReturnFalse; } { return b ? ReturnTrue : ReturnFalse; }
static ALWAYS_INLINE uint getBlockLen(const ushort *&tokPtr); static ALWAYS_INLINE uint getBlockLen(const ushort *&tokPtr);
void evaluateExpression(const ushort *&tokPtr, ProStringList *ret, bool joined); VisitReturn evaluateExpression(const ushort *&tokPtr, ProStringList *ret, bool joined);
static ALWAYS_INLINE void skipStr(const ushort *&tokPtr); static ALWAYS_INLINE void skipStr(const ushort *&tokPtr);
static ALWAYS_INLINE void skipHashStr(const ushort *&tokPtr); static ALWAYS_INLINE void skipHashStr(const ushort *&tokPtr);
void skipExpression(const ushort *&tokPtr); void skipExpression(const ushort *&tokPtr);
@ -173,7 +173,7 @@ public:
VisitReturn visitProLoop(const ProKey &variable, const ushort *exprPtr, VisitReturn visitProLoop(const ProKey &variable, const ushort *exprPtr,
const ushort *tokPtr); const ushort *tokPtr);
void visitProFunctionDef(ushort tok, const ProKey &name, const ushort *tokPtr); void visitProFunctionDef(ushort tok, const ProKey &name, const ushort *tokPtr);
void visitProVariable(ushort tok, const ProStringList &curr, const ushort *&tokPtr); VisitReturn visitProVariable(ushort tok, const ProStringList &curr, const ushort *&tokPtr);
ALWAYS_INLINE const ProKey &map(const ProString &var) { return map(var.toKey()); } ALWAYS_INLINE const ProKey &map(const ProString &var) { return map(var.toKey()); }
const ProKey &map(const ProKey &var); const ProKey &map(const ProKey &var);
@ -182,7 +182,7 @@ public:
void setTemplate(); void setTemplate();
ProStringList split_value_list(const QString &vals, const ProFile *source = 0); ProStringList split_value_list(const QString &vals, const ProFile *source = 0);
ProStringList expandVariableReferences(const ushort *&tokPtr, int sizeHint = 0, bool joined = false); VisitReturn expandVariableReferences(const ushort *&tokPtr, int sizeHint, ProStringList *ret, bool joined);
QString currentFileName() const; QString currentFileName() const;
QString currentDirectory() const; QString currentDirectory() const;
@ -207,14 +207,14 @@ public:
void deprecationWarning(const QString &msg) const void deprecationWarning(const QString &msg) const
{ message(QMakeHandler::EvalWarnDeprecated, msg); } { message(QMakeHandler::EvalWarnDeprecated, msg); }
QList<ProStringList> prepareFunctionArgs(const ushort *&tokPtr); VisitReturn prepareFunctionArgs(const ushort *&tokPtr, QList<ProStringList> *ret);
ProStringList evaluateFunction(const ProFunctionDef &func, VisitReturn evaluateFunction(const ProFunctionDef &func,
const QList<ProStringList> &argumentsList, VisitReturn *ok); const QList<ProStringList> &argumentsList, ProStringList *ret);
VisitReturn evaluateBoolFunction(const ProFunctionDef &func, VisitReturn evaluateBoolFunction(const ProFunctionDef &func,
const QList<ProStringList> &argumentsList, const QList<ProStringList> &argumentsList,
const ProString &function); const ProString &function);
ProStringList evaluateExpandFunction(const ProKey &function, const ushort *&tokPtr); VisitReturn evaluateExpandFunction(const ProKey &function, const ushort *&tokPtr, ProStringList *ret);
VisitReturn evaluateConditionalFunction(const ProKey &function, const ushort *&tokPtr); VisitReturn evaluateConditionalFunction(const ProKey &function, const ushort *&tokPtr);
ProStringList evaluateBuiltinExpand(int func_t, const ProKey &function, const ProStringList &args); ProStringList evaluateBuiltinExpand(int func_t, const ProKey &function, const ProStringList &args);

View File

@ -110,9 +110,8 @@ QStringList QMakeProject::expand(const ProKey &func, const QList<ProStringList>
QHash<ProKey, ProFunctionDef>::ConstIterator it = QHash<ProKey, ProFunctionDef>::ConstIterator it =
m_functionDefs.replaceFunctions.constFind(func); m_functionDefs.replaceFunctions.constFind(func);
if (it != m_functionDefs.replaceFunctions.constEnd()) { if (it != m_functionDefs.replaceFunctions.constEnd()) {
QMakeProject::VisitReturn vr; ProStringList ret;
ProStringList ret = evaluateFunction(*it, args, &vr); if (evaluateFunction(*it, args, &ret) == QMakeProject::ReturnError)
if (vr == QMakeProject::ReturnError)
exit(3); exit(3);
return ret.toQStringList(); return ret.toQStringList();
} }
@ -130,7 +129,9 @@ ProString QMakeProject::expand(const QString &expr, const QString &where, int li
m_current.pro = pro; m_current.pro = pro;
m_current.line = 0; m_current.line = 0;
const ushort *tokPtr = pro->tokPtr(); const ushort *tokPtr = pro->tokPtr();
ProStringList result = expandVariableReferences(tokPtr, 1, true); ProStringList result;
if (expandVariableReferences(tokPtr, 1, &result, true) == ReturnError)
exit(3);
if (!result.isEmpty()) if (!result.isEmpty())
ret = result.at(0); ret = result.at(0);
} }

View File

@ -641,6 +641,72 @@ void tst_qmakelib::addControlStructs()
<< "VAR = final" << "VAR = final"
<< "" << ""
<< true; << true;
QTest::newRow("error() from replace function (assignment)")
<< "defineReplace(func) {\nerror(error)\n}\n"
"VAR = $$func()\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (replacement)")
<< "defineReplace(func) {\nerror(error)\n}\n"
"VAR = $$func()\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (LHS)")
<< "defineReplace(func) {\nerror(error)\nreturn(VAR)\n}\n"
"$$func() = 1\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (loop variable)")
<< "defineReplace(func) {\nerror(error)\nreturn(BLAH)\n}\n"
"for($$func()) {\nVAR = $$BLAH\nbreak()\n}\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (built-in test arguments)")
<< "defineReplace(func) {\nerror(error)\n}\n"
"message($$func()): VAR = 1\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (built-in replace arguments)")
<< "defineReplace(func) {\nerror(error)\n}\n"
"VAR = $$upper($$func())\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (custom test arguments)")
<< "defineReplace(func) {\nerror(error)\n}\n"
"defineTest(custom) {\n}\n"
"custom($$func()): VAR = 1\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
QTest::newRow("error() from replace function (custom replace arguments)")
<< "defineReplace(func) {\nerror(error)\nreturn(1)\n}\n"
"defineReplace(custom) {\nreturn($$1)\n}\n"
"VAR = $$custom($$func(1))\n"
"OKE = 1"
<< "VAR = UNDEF\nOKE = UNDEF"
<< "Project ERROR: error"
<< false;
} }
void tst_qmakelib::addReplaceFunctions(const QString &qindir) void tst_qmakelib::addReplaceFunctions(const QString &qindir)