9768: Fix ast with throw in the middle of return (#2678)

* 9768: Fix ast with throw in the middle of return

```
int f(bool x)
{
    return x ? 0 : throw 0;
}
```

The `throw` part was not included in the ast, leading to an invalid
ternary operator.

* 8526: Fix ast construction for ternary operator

This tries to decide a bit more properly when ':' can be part of a
ternary operator. More precisely, there are some times when we want to
delay the construction of the ast for ':', so that it is place
accordingly to the matching '?'.

Typically, this fixes an issue with
`return val < 0 ? throw 1 : val;`,
where the ast for ':' would be constructed during as part of the
`throw`, and the ast for `?` would be invalid.

This patch is a bit of a hardcode, stating that we don't expect ':'
inside a throw, unless there is a complete ternary operator in there
(there can't be a range based for loop, a case in a switch). When we
reach ':', we know we are and the end of the `throw`.
This commit is contained in:
Daniel Marjamäki 2020-06-15 10:36:02 +02:00 committed by GitHub
commit f5fe5ca2dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 18 additions and 2 deletions

View File

@ -465,8 +465,9 @@ struct AST_state {
bool cpp; bool cpp;
int assign; int assign;
bool inCase; // true from case to : bool inCase; // true from case to :
bool stopAtColon; // help to properly parse ternary operators
const Token *functionCallEndPar; const Token *functionCallEndPar;
explicit AST_state(bool cpp) : depth(0), inArrayAssignment(0), cpp(cpp), assign(0), inCase(false), functionCallEndPar(nullptr) {} explicit AST_state(bool cpp) : depth(0), inArrayAssignment(0), cpp(cpp), assign(0), inCase(false),stopAtColon(false), functionCallEndPar(nullptr) {}
}; };
static Token * skipDecl(Token *tok) static Token * skipDecl(Token *tok)
@ -762,8 +763,13 @@ static void compileTerm(Token *&tok, AST_state& state)
if (Token::Match(tok, "return|case") || (state.cpp && tok->str() == "throw")) { if (Token::Match(tok, "return|case") || (state.cpp && tok->str() == "throw")) {
if (tok->str() == "case") if (tok->str() == "case")
state.inCase = true; state.inCase = true;
const bool tokIsReturn = tok->str() == "return";
const bool stopAtColon = state.stopAtColon;
state.stopAtColon=true;
compileUnaryOp(tok, state, compileExpression); compileUnaryOp(tok, state, compileExpression);
state.op.pop(); state.stopAtColon=stopAtColon;
if (tokIsReturn)
state.op.pop();
if (state.inCase && Token::simpleMatch(tok, ": ;")) { if (state.inCase && Token::simpleMatch(tok, ": ;")) {
state.inCase = false; state.inCase = false;
tok = tok->next(); tok = tok->next();
@ -1209,6 +1215,8 @@ static void compileAssignTernary(Token *&tok, AST_state& state)
// http://en.cppreference.com/w/cpp/language/operator_precedence says about ternary operator: // http://en.cppreference.com/w/cpp/language/operator_precedence says about ternary operator:
// "The expression in the middle of the conditional operator (between ? and :) is parsed as if parenthesized: its precedence relative to ?: is ignored." // "The expression in the middle of the conditional operator (between ? and :) is parsed as if parenthesized: its precedence relative to ?: is ignored."
// Hence, we rely on Tokenizer::prepareTernaryOpForAST() to add such parentheses where necessary. // Hence, we rely on Tokenizer::prepareTernaryOpForAST() to add such parentheses where necessary.
const bool stopAtColon = state.stopAtColon;
state.stopAtColon = false;
if (tok->strAt(1) == ":") { if (tok->strAt(1) == ":") {
state.op.push(nullptr); state.op.push(nullptr);
} }
@ -1216,12 +1224,15 @@ static void compileAssignTernary(Token *&tok, AST_state& state)
state.assign = 0; state.assign = 0;
compileBinOp(tok, state, compileAssignTernary); compileBinOp(tok, state, compileAssignTernary);
state.assign = assign; state.assign = assign;
state.stopAtColon = stopAtColon;
} else if (tok->str() == ":") { } else if (tok->str() == ":") {
if (state.depth == 1U && state.inCase) { if (state.depth == 1U && state.inCase) {
state.inCase = false; state.inCase = false;
tok = tok->next(); tok = tok->next();
break; break;
} }
if (state.stopAtColon)
break;
if (state.assign > 0) if (state.assign > 0)
break; break;
compileBinOp(tok, state, compileAssignTernary); compileBinOp(tok, state, compileAssignTernary);

View File

@ -7484,6 +7484,10 @@ private:
ASSERT_EQUALS("fabc,de,:?=", testAst("f = (a ? b, c : (d, e));")); ASSERT_EQUALS("fabc,de,:?=", testAst("f = (a ? b, c : (d, e));"));
ASSERT_EQUALS("ab35,4:?foo(:?return", testAst("return (a ? b ? (3,5) : 4 : foo());")); ASSERT_EQUALS("ab35,4:?foo(:?return", testAst("return (a ? b ? (3,5) : 4 : foo());"));
ASSERT_EQUALS("check(result_type00,{invalid:?return", testAst("return check() ? result_type {0, 0} : invalid;")); ASSERT_EQUALS("check(result_type00,{invalid:?return", testAst("return check() ? result_type {0, 0} : invalid;"));
ASSERT_EQUALS("x01:?return", testAst("return x ? 0 : 1;"));
ASSERT_EQUALS("x00throw:?return", testAst("return x ? 0 : throw 0;")); // #9768
ASSERT_EQUALS("val0<1throwval:?return", testAst("return val < 0 ? throw 1 : val;")); // #8526
ASSERT_EQUALS("ix0<00throw:?=", testAst("int i = x < 0 ? 0 : throw 0;"));
ASSERT_EQUALS("a\"\"=", testAst("a=\"\"")); ASSERT_EQUALS("a\"\"=", testAst("a=\"\""));
ASSERT_EQUALS("a\'\'=", testAst("a=\'\'")); ASSERT_EQUALS("a\'\'=", testAst("a=\'\'"));
@ -7927,6 +7931,7 @@ private:
ASSERT_EQUALS("0case", testAst("case 0:")); ASSERT_EQUALS("0case", testAst("case 0:"));
ASSERT_EQUALS("12+case", testAst("case 1+2:")); ASSERT_EQUALS("12+case", testAst("case 1+2:"));
ASSERT_EQUALS("xyz:?case", testAst("case (x?y:z):")); ASSERT_EQUALS("xyz:?case", testAst("case (x?y:z):"));
ASSERT_EQUALS("switchx( 1case y++ 2case", testAst("switch(x){case 1:{++y;break;case 2:break;}}"));
} }
void astrefqualifier() { void astrefqualifier() {