From 7ddb7aef7d23c8294dc315ae5b2c5e7474d064d2 Mon Sep 17 00:00:00 2001 From: Ken-Patrick Lehrmann Date: Sun, 14 Jun 2020 18:45:19 +0200 Subject: [PATCH] 8526: Fix ast construction for ternary operator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- lib/tokenlist.cpp | 11 ++++++++++- test/testtokenize.cpp | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index a8868cd5c..00a8fdd72 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -465,8 +465,9 @@ struct AST_state { bool cpp; int assign; bool inCase; // true from case to : + bool stopAtColon; // help to properly parse ternary operators 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) @@ -763,7 +764,10 @@ static void compileTerm(Token *&tok, AST_state& state) if (tok->str() == "case") state.inCase = true; const bool tokIsReturn = tok->str() == "return"; + const bool stopAtColon = state.stopAtColon; + state.stopAtColon=true; compileUnaryOp(tok, state, compileExpression); + state.stopAtColon=stopAtColon; if (tokIsReturn) state.op.pop(); if (state.inCase && Token::simpleMatch(tok, ": ;")) { @@ -1211,6 +1215,8 @@ static void compileAssignTernary(Token *&tok, AST_state& state) // 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." // Hence, we rely on Tokenizer::prepareTernaryOpForAST() to add such parentheses where necessary. + const bool stopAtColon = state.stopAtColon; + state.stopAtColon = false; if (tok->strAt(1) == ":") { state.op.push(nullptr); } @@ -1218,12 +1224,15 @@ static void compileAssignTernary(Token *&tok, AST_state& state) state.assign = 0; compileBinOp(tok, state, compileAssignTernary); state.assign = assign; + state.stopAtColon = stopAtColon; } else if (tok->str() == ":") { if (state.depth == 1U && state.inCase) { state.inCase = false; tok = tok->next(); break; } + if (state.stopAtColon) + break; if (state.assign > 0) break; compileBinOp(tok, state, compileAssignTernary); diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index 4cb4e1e15..e2e2c8197 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -7486,6 +7486,8 @@ private: 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=\'\'"));