Skip to content

Commit 6a01fa9

Browse files
#11134 Fix broken AST with (designated) initializers (danmar#4550)
* Make control flow a bit easier, and more similar to previous code Made similar to around line 790 * In a cpp11init, always parse only the corresponding } (#11134) - _always_, because in some cases this was omitted (around line 790) or too strict (around line 860) - _only_, and not following tokens which happen to be } as well (around line 1030) * Fix unit tests: AST was incorrect, now is fixed auto var{ {{},{}}, {} }; Old AST: ``` { |-var `-{ `-, |-, | |-{ | `-{ `-{ ``` New AST: ``` { |-var `-, |-{ | `-, | | |-{ | | `-{ `-{ ``` Compare the same example, but with `X{}` instead of just `{}`: `auto var{ a{b{},c{}}, d{} };` ``` { |-var `-, |-{ | |-a | `-, | | |-{ | | | `-b | | `-{ | | | `-c `-{ `-d ``` This structure is similar to that of the new AST, not the old AST * Fix unit tests: another AST was incorrect, now is fixed Code: `auto var{{1,a::b{2,3}}, {4,a::b{5,6}}};` Old AST: ``` { |-var `-{ `-, |-, | |-1 'signed int' | `-{ | | |-:: | | | |-a | | | `-b | | `-, | | | |-2 'signed int' | | | `-3 'signed int' `-{ `-, |-4 'signed int' `-{ |-:: | |-a | `-b `-, |-5 'signed int' `-6 'signed int' ``` New AST: ``` { |-var `-, |-{ | `-, | | |-1 'signed int' | | `-{ | | | |-:: | | | | |-a | | | | `-b | | | `-, | | | | |-2 'signed int' | | | | `-3 'signed int' `-{ `-, |-4 'signed int' `-{ |-:: | |-a | `-b `-, |-5 'signed int' `-6 'signed int' ``` * Fix unit tests: missing ; after class, resulting in incorrectly being marked as cpp11init Because of the missing `;` after the class declaration, it was marked as a cpp11init block. Which it isn't, and which now throws an exception * Fix cpp11init to let unit tests pass again The following unit tests failed on the newly introduced throws, because the code for these tests incorrectly marked some tokens as cpp11init: TestVarID::varid_cpp11initialization TestTokenizer::checkRefQualifiers * Fix typo * Improve check for void trailing return type Observation: the only function body _not_ containing a semicolon, is a void function: something like auto make_zero(ini& i) -> void { while(--i > 0) {} } Non-void function? Then it must return a value, and thus contain a semicolon, which is checked for a few lines later. * Fix cpp11init with templated trailing return type In the following example, vector was marked as cpp11init due to the mismatch of `%any% {` auto f() -> std::vector<int> { return {}; } I made the assumption that whenever "%any% {" matches, endtok must be set too. If this assumtion doesn't hold (so "%any% {" matches, but endtok == nullptr), then the for-loop would search all the way to the end of stream. Which I guess was not the intention. * Remove comments Co-authored-by: Gerbo Engels <[email protected]>
1 parent 5a8e55c commit 6a01fa9

6 files changed

Lines changed: 117 additions & 36 deletions

File tree

lib/token.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ Token* findLambdaEndScope(Token* tok)
24822482
if (!Token::simpleMatch(tok, ")"))
24832483
return nullptr;
24842484
tok = tok->next();
2485-
while (Token::Match(tok, "mutable|constexpr|constval|noexcept|.")) {
2485+
while (Token::Match(tok, "mutable|constexpr|consteval|noexcept|.")) {
24862486
if (Token::simpleMatch(tok, "noexcept ("))
24872487
tok = tok->linkAt(1);
24882488
if (Token::simpleMatch(tok, ".")) {

lib/tokenlist.cpp

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,11 @@ static bool iscpp11init_impl(const Token * const tok)
652652
return false;
653653
if (Token::Match(nameToken, "else|try|do|const|constexpr|override|volatile|&|&&"))
654654
return false;
655+
if (Token::simpleMatch(nameToken->previous(), ". void {") && nameToken->previous()->originalName() == "->")
656+
return false; // trailing return type. The only function body that can contain no semicolon is a void function.
655657
if (Token::simpleMatch(nameToken->previous(), "namespace"))
656658
return false;
657-
if (Token::Match(nameToken, "%any% {") && !Token::Match(nameToken, "return|:")) {
659+
if (endtok != nullptr && !Token::Match(nameToken, "return|:")) {
658660
// If there is semicolon between {..} this is not a initlist
659661
for (const Token *tok2 = nameToken->next(); tok2 != endtok; tok2 = tok2->next()) {
660662
if (tok2->str() == ";")
@@ -668,7 +670,7 @@ static bool iscpp11init_impl(const Token * const tok)
668670
if (!Token::simpleMatch(endtok, "} ;"))
669671
return true;
670672
const Token *prev = nameToken;
671-
while (Token::Match(prev, "%name%|::|:|<|>")) {
673+
while (Token::Match(prev, "%name%|::|:|<|>|,")) {
672674
if (Token::Match(prev, "class|struct"))
673675
return false;
674676

@@ -785,10 +787,15 @@ static void compileTerm(Token *&tok, AST_state& state)
785787
tok = tok->link()->next();
786788

787789
if (Token::Match(tok, "{ . %name% =|{")) {
790+
const Token* end = tok->link();
788791
const int inArrayAssignment = state.inArrayAssignment;
789792
state.inArrayAssignment = 1;
790793
compileBinOp(tok, state, compileExpression);
791794
state.inArrayAssignment = inArrayAssignment;
795+
if (tok == end)
796+
tok = tok->next();
797+
else
798+
throw InternalError(tok, "Syntax error. Unexpected tokens in designated initializer.", InternalError::AST);
792799
} else if (Token::simpleMatch(tok, "{ }")) {
793800
tok->astOperand1(state.op.top());
794801
state.op.pop();
@@ -834,26 +841,26 @@ static void compileTerm(Token *&tok, AST_state& state)
834841
if (Token::simpleMatch(tok->link(),"} [")) {
835842
tok = tok->next();
836843
} else if (state.cpp && iscpp11init(tok)) {
844+
Token *const end = tok->link();
837845
if (state.op.empty() || Token::Match(tok->previous(), "[{,]") || Token::Match(tok->tokAt(-2), "%name% (")) {
838-
if (Token::Match(tok, "{ !!}")) {
839-
Token *const end = tok->link();
840-
if (Token::Match(tok, "{ . %name% =|{")) {
841-
const int inArrayAssignment = state.inArrayAssignment;
842-
state.inArrayAssignment = 1;
843-
compileBinOp(tok, state, compileExpression);
844-
state.inArrayAssignment = inArrayAssignment;
845-
} else {
846-
compileUnaryOp(tok, state, compileExpression);
847-
}
848-
if (precedes(tok,end))
849-
tok = end;
850-
} else {
846+
if (Token::Match(tok, "{ . %name% =|{")) {
847+
const int inArrayAssignment = state.inArrayAssignment;
848+
state.inArrayAssignment = 1;
849+
compileBinOp(tok, state, compileExpression);
850+
state.inArrayAssignment = inArrayAssignment;
851+
} else if (Token::simpleMatch(tok, "{ }")) {
851852
state.op.push(tok);
852-
tok = tok->tokAt(2);
853+
tok = tok->next();
854+
} else {
855+
compileUnaryOp(tok, state, compileExpression);
856+
if (precedes(tok,end)) // typically for something like `MACRO(x, { if (c) { ... } })`, where end is the last curly, and tok is the open curly for the if
857+
tok = end;
853858
}
854859
} else
855860
compileBinOp(tok, state, compileExpression);
856-
if (Token::Match(tok, "} ,|:|)"))
861+
if (tok != end)
862+
throw InternalError(tok, "Syntax error. Unexpected tokens in initializer.", InternalError::AST);
863+
if (tok->next())
857864
tok = tok->next();
858865
} else if (state.cpp && Token::Match(tok->tokAt(-2), "%name% ( {") && !Token::findsimplematch(tok, ";", tok->link())) {
859866
if (Token::simpleMatch(tok, "{ }"))
@@ -966,7 +973,7 @@ static void compilePrecedence2(Token *&tok, AST_state& state)
966973
if (Token::simpleMatch(squareBracket->link(), "] (")) {
967974
Token* const roundBracket = squareBracket->link()->next();
968975
Token* curlyBracket = roundBracket->link()->next();
969-
while (Token::Match(curlyBracket, "mutable|const|constexpr"))
976+
while (Token::Match(curlyBracket, "mutable|const|constexpr|consteval"))
970977
curlyBracket = curlyBracket->next();
971978
if (Token::simpleMatch(curlyBracket, "noexcept ("))
972979
curlyBracket = curlyBracket->linkAt(1)->next();
@@ -1025,12 +1032,20 @@ static void compilePrecedence2(Token *&tok, AST_state& state)
10251032
cast->astOperand1(tok1);
10261033
tok = tok1->link()->next();
10271034
} else if (state.cpp && tok->str() == "{" && iscpp11init(tok)) {
1035+
const Token* end = tok->link();
10281036
if (Token::simpleMatch(tok, "{ }"))
1029-
compileUnaryOp(tok, state, compileExpression);
1037+
{
1038+
compileUnaryOp(tok, state, nullptr);
1039+
tok = tok->next();
1040+
}
10301041
else
1042+
{
10311043
compileBinOp(tok, state, compileExpression);
1032-
while (Token::simpleMatch(tok, "}"))
1033-
tok = tok->next();
1044+
}
1045+
if (tok == end)
1046+
tok = end->next();
1047+
else
1048+
throw InternalError(tok, "Syntax error. Unexpected tokens in initializer.", InternalError::AST);
10341049
} else break;
10351050
}
10361051
}

test/testsimplifytemplate.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4854,14 +4854,14 @@ class TestSimplifyTemplate : public TestFixture {
48544854
// "no constructor" false positives
48554855
const char code[] = "class Fred {\n"
48564856
" template<class T> explicit Fred(T t) { }\n"
4857-
"}";
4858-
ASSERT_EQUALS("class Fred { template < class T > explicit Fred ( T t ) { } }", tok(code));
4857+
"};";
4858+
ASSERT_EQUALS("class Fred { template < class T > explicit Fred ( T t ) { } } ;", tok(code));
48594859

48604860
// #3532
48614861
const char code2[] = "class Fred {\n"
48624862
" template<class T> Fred(T t) { }\n"
4863-
"}";
4864-
ASSERT_EQUALS("class Fred { template < class T > Fred ( T t ) { } }", tok(code2));
4863+
"};";
4864+
ASSERT_EQUALS("class Fred { template < class T > Fred ( T t ) { } } ;", tok(code2));
48654865
}
48664866

48674867
void syntax_error_templates_1() {

test/testsimplifytypedef.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,14 +1427,14 @@ class TestSimplifyTypedef : public TestFixture {
14271427
"typedef const Class & Const_Reference;\n"
14281428
"void some_method (Const_Reference x) const {}\n"
14291429
"void another_method (Const_Reference x) const {}\n"
1430-
"}";
1430+
"};";
14311431

14321432
// The expected result..
14331433
const char expected[] = "class Class2 { "
14341434
""
14351435
"void some_method ( const Class & x ) const { } "
14361436
"void another_method ( const Class & x ) const { } "
1437-
"}";
1437+
"} ;";
14381438
ASSERT_EQUALS(expected, tok(code));
14391439
}
14401440

test/testtokenize.cpp

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ class TestTokenizer : public TestFixture {
461461
TEST_CASE(simplifyIfSwitchForInit5);
462462

463463
TEST_CASE(cpp20_default_bitfield_initializer);
464+
465+
TEST_CASE(cpp11init);
464466
}
465467

466468
#define tokenizeAndStringify(...) tokenizeAndStringify_(__FILE__, __LINE__, __VA_ARGS__)
@@ -6228,8 +6230,8 @@ class TestTokenizer : public TestFixture {
62286230
ASSERT_EQUALS("abR{{,P(,((", testAst("a(b(R{},{},P()));"));
62296231
ASSERT_EQUALS("f1{2{,3{,{x,(", testAst("f({{1},{2},{3}},x);"));
62306232
ASSERT_EQUALS("a1{ b2{", testAst("auto a{1}; auto b{2};"));
6231-
ASSERT_EQUALS("var1ab::23,{,4ab::56,{,{,{{", testAst("auto var{{1,a::b{2,3}}, {4,a::b{5,6}}};"));
6232-
ASSERT_EQUALS("var{{,{,{{", testAst("auto var{ {{},{}}, {} };"));
6233+
ASSERT_EQUALS("var1ab::23,{,{4ab::56,{,{,{", testAst("auto var{{1,a::b{2,3}}, {4,a::b{5,6}}};"));
6234+
ASSERT_EQUALS("var{{,{{,{", testAst("auto var{ {{},{}}, {} };"));
62336235
ASSERT_EQUALS("fXYabcfalse==CD:?,{,{(", testAst("f({X, {Y, abc == false ? C : D}});"));
62346236
ASSERT_EQUALS("stdvector::p0[{(return", testAst("return std::vector<int>({ p[0] });"));
62356237

@@ -6465,7 +6467,7 @@ class TestTokenizer : public TestFixture {
64656467
"}"));
64666468
ASSERT_EQUALS("{(=[{return ab=",
64676469
testAst("return {\n"
6468-
" [=]() mutable -> int {\n"
6470+
" [=]() mutable consteval -> int {\n"
64696471
" a=b;\n"
64706472
" }\n"
64716473
"}"));
@@ -6690,12 +6692,31 @@ class TestTokenizer : public TestFixture {
66906692
"}; "
66916693
"struct poc p = { .port[0] = {.d = 3} };"));
66926694

6693-
// op op
6694-
ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { dostuff (x==>y); }"), InternalError, "syntax error: == >");
6695-
66966695
// Ticket #9664
66976696
ASSERT_NO_THROW(tokenizeAndStringify("S s = { .x { 2 }, .y[0] { 3 } };"));
66986697

6698+
// Ticket #11134
6699+
ASSERT_NO_THROW(tokenizeAndStringify("struct my_struct { int x; }; "
6700+
"std::string s; "
6701+
"func(my_struct{ .x=42 }, s.size());"));
6702+
ASSERT_NO_THROW(tokenizeAndStringify("struct my_struct { int x; int y; }; "
6703+
"std::string s; "
6704+
"func(my_struct{ .x{42}, .y=3 }, s.size());"));
6705+
ASSERT_NO_THROW(tokenizeAndStringify("struct my_struct { int x; int y; }; "
6706+
"std::string s; "
6707+
"func(my_struct{ .x=42, .y{3} }, s.size());"));
6708+
ASSERT_NO_THROW(tokenizeAndStringify("struct my_struct { int x; }; "
6709+
"void h() { "
6710+
" for (my_struct ms : { my_struct{ .x=5 } }) {} "
6711+
"}"));
6712+
ASSERT_NO_THROW(tokenizeAndStringify("struct my_struct { int x; int y; }; "
6713+
"void h() { "
6714+
" for (my_struct ms : { my_struct{ .x=5, .y{42} } }) {} "
6715+
"}"));
6716+
6717+
// op op
6718+
ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { dostuff (x==>y); }"), InternalError, "syntax error: == >");
6719+
66996720
ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a==()); }"), InternalError, "syntax error: ==()");
67006721
ASSERT_THROW_EQUALS(tokenizeAndStringify("void f() { assert(a+()); }"), InternalError, "syntax error: +()");
67016722

@@ -7382,6 +7403,51 @@ class TestTokenizer : public TestFixture {
73827403
settings.standards.cpp = Standards::CPP17;
73837404
ASSERT_THROW(tokenizeAndStringify(code, settings), InternalError);
73847405
}
7406+
7407+
void cpp11init() {
7408+
#define testIsCpp11init(...) testIsCpp11init_(__FILE__, __LINE__, __VA_ARGS__)
7409+
auto testIsCpp11init_ = [this](const char* file, int line, const char* code, const char* find, TokenImpl::Cpp11init expected) {
7410+
Settings settings;
7411+
Tokenizer tokenizer(&settings, this);
7412+
std::istringstream istr(code);
7413+
ASSERT_LOC(tokenizer.tokenize(istr, "test.cpp"), file, line);
7414+
7415+
const Token* tok = Token::findsimplematch(tokenizer.tokens(), find, strlen(find));
7416+
ASSERT_LOC(tok, file, line);
7417+
ASSERT_LOC(tok->isCpp11init() == expected, file, line);
7418+
};
7419+
7420+
testIsCpp11init("class X : public A<int>, C::D {};",
7421+
"D {",
7422+
TokenImpl::Cpp11init::NOINIT);
7423+
7424+
testIsCpp11init("auto f() -> void {}",
7425+
"void {",
7426+
TokenImpl::Cpp11init::NOINIT);
7427+
testIsCpp11init("auto f() & -> void {}",
7428+
"void {",
7429+
TokenImpl::Cpp11init::NOINIT);
7430+
testIsCpp11init("auto f() const noexcept(false) -> void {}",
7431+
"void {",
7432+
TokenImpl::Cpp11init::NOINIT);
7433+
testIsCpp11init("auto f() -> std::vector<int> { return {}; }",
7434+
"{ return",
7435+
TokenImpl::Cpp11init::NOINIT);
7436+
testIsCpp11init("auto f() -> std::vector<int> { return {}; }",
7437+
"vector",
7438+
TokenImpl::Cpp11init::NOINIT);
7439+
testIsCpp11init("auto f() -> std::vector<int> { return {}; }",
7440+
"std ::",
7441+
TokenImpl::Cpp11init::NOINIT);
7442+
7443+
testIsCpp11init("class X{};",
7444+
"{ }",
7445+
TokenImpl::Cpp11init::NOINIT);
7446+
testIsCpp11init("class X{}", // forgotten ; so not properly recognized as a class
7447+
"{ }",
7448+
TokenImpl::Cpp11init::CPP11INIT);
7449+
#undef testIsCpp11init
7450+
}
73857451
};
73867452

73877453
REGISTER_TEST(TestTokenizer)

test/testvarid.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,14 +790,14 @@ class TestVarID : public TestFixture {
790790
" : ExecutionPath(c, id)\n"
791791
" {\n"
792792
" }\n"
793-
"}\n";
793+
"};\n";
794794
const char expected3[] = "1: class Nullpointer : public ExecutionPath\n"
795795
"2: {\n"
796796
"3: Nullpointer ( Check * c@1 , const unsigned int id@2 , const std :: string & name@3 )\n"
797797
"4: : ExecutionPath ( c@1 , id@2 )\n"
798798
"5: {\n"
799799
"6: }\n"
800-
"7: }\n";
800+
"7: } ;\n";
801801
ASSERT_EQUALS(expected3, tokenize(code3));
802802
}
803803

0 commit comments

Comments
 (0)