Fix #12210 (Cppcheck hang in SymbolDatabase::createSymbolDatabaseExprIds) (#5699)

This commit is contained in:
Daniel Marjamäki 2023-12-05 14:22:32 +01:00 committed by GitHub
parent 5e7f2bd904
commit 70745b527a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 381 additions and 183 deletions

View File

@ -887,7 +887,7 @@ test/testvaarg.o: test/testvaarg.cpp lib/addoninfo.h lib/check.h lib/checkvaarg.
test/testvalueflow.o: test/testvalueflow.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp
test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h
test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvarid.cpp
externals/simplecpp/simplecpp.o: externals/simplecpp/simplecpp.cpp externals/simplecpp/simplecpp.h

View File

@ -1571,16 +1571,101 @@ static std::string getIncompleteNameID(const Token* tok)
return result + tok->expressionString();
}
namespace {
struct ExprIdKey {
std::string parentOp;
nonneg int operand1;
nonneg int operand2;
bool operator<(const ExprIdKey& k) const {
return std::tie(parentOp, operand1, operand2) < std::tie(k.parentOp, k.operand1, k.operand2);
}
};
using ExprIdMap = std::map<ExprIdKey, nonneg int>;
void setParentExprId(Token* tok, bool cpp, ExprIdMap& exprIdMap, nonneg int &id) {
if (!tok->astParent() || tok->astParent()->isControlFlowKeyword())
return;
const Token* op1 = tok->astParent()->astOperand1();
if (op1 && op1->exprId() == 0)
return;
const Token* op2 = tok->astParent()->astOperand2();
if (op2 && op2->exprId() == 0)
return;
if (tok->astParent()->isExpandedMacro() || Token::Match(tok->astParent(), "++|--")) {
tok->astParent()->exprId(id);
++id;
setParentExprId(tok->astParent(), cpp, exprIdMap, id);
return;
}
ExprIdKey key;
key.parentOp = tok->astParent()->str();
key.operand1 = op1 ? op1->exprId() : 0;
key.operand2 = op2 ? op2->exprId() : 0;
if (tok->astParent()->isCast() && tok->astParent()->str() == "(") {
const Token* typeStartToken;
const Token* typeEndToken;
if (tok->astParent()->astOperand2()) {
typeStartToken = tok->astParent()->astOperand1();
typeEndToken = tok;
} else {
typeStartToken = tok->astParent()->next();
typeEndToken = tok->astParent()->link();
}
std::string type;
for (const Token* t = typeStartToken; t != typeEndToken; t = t->next()) {
type += " " + t->str();
}
key.parentOp += type;
}
for (const auto& ref: followAllReferences(op1)) {
if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm
key.operand1 = ref.token->exprId();
break;
}
}
for (const auto& ref: followAllReferences(op2)) {
if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm
key.operand2 = ref.token->exprId();
break;
}
}
if (key.operand1 > key.operand2 && key.operand2 &&
Token::Match(tok->astParent(), "%or%|%oror%|+|*|&|&&|^|==|!=")) {
// In C++ the order of operands of + might matter
if (!cpp ||
key.parentOp != "+" ||
!tok->astParent()->valueType() ||
tok->astParent()->valueType()->isIntegral() ||
tok->astParent()->valueType()->isFloat() ||
tok->astParent()->valueType()->pointer > 0)
std::swap(key.operand1, key.operand2);
}
const auto it = exprIdMap.find(key);
if (it == exprIdMap.end()) {
exprIdMap[key] = id;
tok->astParent()->exprId(id);
++id;
} else {
tok->astParent()->exprId(it->second);
}
setParentExprId(tok->astParent(), cpp, exprIdMap, id);
}
}
void SymbolDatabase::createSymbolDatabaseExprIds()
{
nonneg int base = 0;
// Find highest varId
for (const Variable *var : mVariableList) {
if (!var)
continue;
base = std::max<MathLib::bigint>(base, var->declarationId());
nonneg int maximumVarId = 0;
for (const Token* tok = mTokenizer.list.front(); tok; tok = tok->next()) {
if (tok->varId() > maximumVarId)
maximumVarId = tok->varId();
}
nonneg int id = base + 1;
nonneg int id = maximumVarId + 1;
// Find incomplete vars that are used in constant context
std::unordered_map<std::string, nonneg int> unknownConstantIds;
const Token* inConstExpr = nullptr;
@ -1611,7 +1696,6 @@ void SymbolDatabase::createSymbolDatabaseExprIds()
});
for (const Scope * scope : exprScopes) {
nonneg int thisId = 0;
std::unordered_map<std::string, std::vector<Token*>> exprs;
std::unordered_map<std::string, nonneg int> unknownIds;
@ -1638,62 +1722,65 @@ void SymbolDatabase::createSymbolDatabaseExprIds()
}
// Assign IDs
ExprIdMap exprIdMap;
std::map<std::string, nonneg int> baseIds;
for (Token* tok = const_cast<Token*>(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) {
if (tok->varId() > 0) {
tok->exprId(tok->varId());
} else if (isExpression(tok)) {
exprs[tok->str()].push_back(tok);
tok->exprId(id++);
if (id == std::numeric_limits<nonneg int>::max() / 4) {
throw InternalError(nullptr, "Ran out of expression ids.", InternalError::INTERNAL);
}
} else if (isCPP() && Token::simpleMatch(tok, "this")) {
if (thisId == 0)
thisId = id++;
tok->exprId(thisId);
}
}
// Apply CSE
for (const auto& p:exprs) {
const std::vector<Token*>& tokens = p.second;
const std::size_t N = tokens.size();
for (std::size_t i = 0; i < N; ++i) {
Token* const tok1 = tokens[i];
for (std::size_t j = i + 1; j < N; ++j) {
Token* const tok2 = tokens[j];
if (tok1->exprId() == tok2->exprId())
continue;
if (!isSameExpression(isCPP(), true, tok1, tok2, mSettings.library, false, false))
continue;
nonneg int const cid = std::min(tok1->exprId(), tok2->exprId());
tok1->exprId(cid);
tok2->exprId(cid);
}
}
}
// Mark expressions that are unique
std::unordered_map<nonneg int, Token*> exprMap;
for (Token* tok = const_cast<Token*>(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) {
if (tok->exprId() == 0)
continue;
auto p = exprMap.emplace(tok->exprId(), tok);
// Already exists so set it to null
if (!p.second) {
p.first->second = nullptr;
}
}
for (const auto& p : exprMap) {
if (!p.second)
continue;
if (p.second->variable()) {
const Variable* var = p.second->variable();
if (var->nameToken() != p.second)
if (tok->astParent() && tok->astParent()->exprId() == 0)
setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id);
} else if (tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) {
if (tok->tokType() == Token::Type::eBracket)
continue;
if (tok->astParent()->isAssignmentOp())
continue;
if (tok->isControlFlowKeyword())
continue;
if (Token::Match(tok, "%name% <") && tok->next()->link()) {
tok->exprId(id);
++id;
} else {
const auto it = baseIds.find(tok->str());
if (it != baseIds.end()) {
tok->exprId(it->second);
} else {
baseIds[tok->str()] = id;
tok->exprId(id);
++id;
}
}
setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id);
}
p.second->setUniqueExprId();
}
for (Token* tok = const_cast<Token*>(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) {
if (tok->varId() == 0 && tok->exprId() > 0 && tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) {
if (tok->isNumber() || tok->isKeyword() || Token::Match(tok->astParent(), ".|::") ||
(Token::simpleMatch(tok->astParent(), "(") && precedes(tok, tok->astParent())))
tok->exprId(0);
}
}
}
// Mark expressions that are unique
std::vector<std::pair<Token*, int>> uniqueExprId(id);
for (Token* tok = const_cast<Token*>(mTokenizer.list.front()); tok; tok = tok->next()) {
const auto id2 = tok->exprId();
if (id2 == 0 || id2 <= maximumVarId)
continue;
uniqueExprId[id2].first = tok;
uniqueExprId[id2].second++;
}
for (const auto& p : uniqueExprId) {
if (!p.first || p.second != 1)
continue;
if (p.first->variable()) {
const Variable* var = p.first->variable();
if (var->nameToken() != p.first)
continue;
}
p.first->setUniqueExprId();
}
}

View File

@ -1269,7 +1269,10 @@ std::string Token::stringify(const stringifyOptions& options) const
} else if (options.exprid && mImpl->mExprId != 0) {
ret += '@';
ret += (options.idtype ? "expr" : "");
ret += std::to_string(mImpl->mExprId);
if ((mImpl->mExprId & (1U << efIsUnique)) != 0)
ret += "UNIQUE";
else
ret += std::to_string(mImpl->mExprId);
}
return ret;

View File

@ -907,10 +907,7 @@ public:
bool isUniqueExprId() const
{
if (mImpl->mExprId > 0) {
return (mImpl->mExprId & (1 << efIsUnique)) != 0;
}
return false;
return (mImpl->mExprId & (1 << efIsUnique)) != 0;
}
/**

View File

@ -153,115 +153,6 @@ def test_progress_j(tmpdir):
assert stderr == ""
@pytest.mark.timeout(10)
def test_slow_array_many_floats(tmpdir):
# 11649
# cppcheck valueflow takes a long time when an array has many floats
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("const float f[] = {\n")
for i in range(20000):
f.write(' 13.6f,\n')
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(10)
def test_slow_array_many_strings(tmpdir):
# 11901
# cppcheck valueflow takes a long time when analyzing a file with many strings
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("const char *strings[] = {\n")
for i in range(20000):
f.write(' "abc",\n')
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(10)
def test_slow_long_line(tmpdir):
# simplecpp #314
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("#define A() static const int a[] = {\\\n")
for i in range(5000):
f.write(" -123, 456, -789,\\\n")
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(60)
def test_slow_large_constant_expression(tmpdir):
# 12182
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("""
#define FLAG1 0
#define FLAG2 0
#define FLAG3 0
#define FLAG4 0
#define FLAG5 0
#define FLAG6 0
#define FLAG7 0
#define FLAG8 0
#define FLAG9 0
#define FLAG10 0
#define FLAG11 0
#define FLAG12 0
#define FLAG13 0
#define FLAG14 0
#define FLAG15 0
#define FLAG16 0
#define FLAG17 0
#define FLAG18 0
#define FLAG19 0
#define FLAG20 0
#define FLAG21 0
#define FLAG22 0
#define FLAG23 0
#define FLAG24 0
#define maxval(x, y) ((x) > (y) ? (x) : (y))
#define E_SAMPLE_SIZE maxval( FLAG1, \
maxval( FLAG2, \
maxval( FLAG3, \
maxval( FLAG4, \
maxval( FLAG5, \
maxval( FLAG6, \
maxval( FLAG7, \
maxval( FLAG8, \
maxval( FLAG9, \
maxval( FLAG10, \
maxval( FLAG11, \
maxval( FLAG12, \
maxval( FLAG13, \
maxval( FLAG14, \
FLAG15 ))))))))))))))
#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \
maxval( sizeof(st), \
maxval( FLAG16, \
maxval( FLAG17, \
maxval( FLAG18, \
maxval( FLAG19, \
maxval( FLAG20, \
maxval( FLAG21, \
maxval( FLAG22, \
maxval( FLAG23, \
FLAG24 ))))))))))
typedef struct {
int n;
} st;
x = SAMPLE_SIZE;
""")
cppcheck([filename])
def test_execute_addon_failure(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
with open(test_file, 'wt') as f:
@ -852,7 +743,7 @@ inline void f2()
##file {}
2: void f2 ( )
3: {{
4: int i@var1 ; i@var1 =@expr1073741828 0 ;
4: int i@var1 ; i@var1 = 0 ;
5: }}
##file {}
@ -861,7 +752,7 @@ inline void f2()
2:
3: void f1 ( )
4: {{
5: int i@var2 ; i@var2 =@expr1073741829 0 ;
5: int i@var2 ; i@var2 = 0 ;
6: }}
##file {}
@ -871,7 +762,7 @@ inline void f2()
3:
4: void f ( )
5: {{
6: int i@var3 ; i@var3 =@expr1073741830 0 ;
6: int i@var3 ; i@var3 = 0 ;
7: }}

View File

@ -0,0 +1,149 @@
# python -m pytest test-other.py
import os
import sys
import pytest
from testutils import cppcheck, assert_cppcheck
@pytest.mark.timeout(10)
def test_slow_array_many_floats(tmpdir):
# 11649
# cppcheck valueflow takes a long time when an array has many floats
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("const float f[] = {\n")
for i in range(20000):
f.write(' 13.6f,\n')
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(10)
def test_slow_array_many_strings(tmpdir):
# 11901
# cppcheck valueflow takes a long time when analyzing a file with many strings
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("const char *strings[] = {\n")
for i in range(20000):
f.write(' "abc",\n')
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(10)
def test_slow_long_line(tmpdir):
# simplecpp #314
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("#define A() static const int a[] = {\\\n")
for i in range(5000):
f.write(" -123, 456, -789,\\\n")
f.write("};\n")
cppcheck([filename]) # should not take more than ~1 second
@pytest.mark.timeout(60)
def test_slow_large_constant_expression(tmpdir):
# 12182
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("""
#define FLAG1 0
#define FLAG2 0
#define FLAG3 0
#define FLAG4 0
#define FLAG5 0
#define FLAG6 0
#define FLAG7 0
#define FLAG8 0
#define FLAG9 0
#define FLAG10 0
#define FLAG11 0
#define FLAG12 0
#define FLAG13 0
#define FLAG14 0
#define FLAG15 0
#define FLAG16 0
#define FLAG17 0
#define FLAG18 0
#define FLAG19 0
#define FLAG20 0
#define FLAG21 0
#define FLAG22 0
#define FLAG23 0
#define FLAG24 0
#define maxval(x, y) ((x) > (y) ? (x) : (y))
#define E_SAMPLE_SIZE maxval( FLAG1, \
maxval( FLAG2, \
maxval( FLAG3, \
maxval( FLAG4, \
maxval( FLAG5, \
maxval( FLAG6, \
maxval( FLAG7, \
maxval( FLAG8, \
maxval( FLAG9, \
maxval( FLAG10, \
maxval( FLAG11, \
maxval( FLAG12, \
maxval( FLAG13, \
maxval( FLAG14, \
FLAG15 ))))))))))))))
#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \
maxval( sizeof(st), \
maxval( FLAG16, \
maxval( FLAG17, \
maxval( FLAG18, \
maxval( FLAG19, \
maxval( FLAG20, \
maxval( FLAG21, \
maxval( FLAG22, \
maxval( FLAG23, \
FLAG24 ))))))))))
typedef struct {
int n;
} st;
x = SAMPLE_SIZE;
""")
cppcheck([filename])
@pytest.mark.timeout(10)
def test_slow_exprid(tmpdir):
# 11885
filename = os.path.join(tmpdir, 'hang.c')
with open(filename, 'wt') as f:
f.write("""
int foo(int a, int b)
{
#define A0(a, b) ((a) + (b))
#define A1(a, b) ((a) > (b)) ? A0((a) - (b), (b)) : A0((b) - (a), (a))
#define A2(a, b) ((a) > (b)) ? A1((a) - (b), (b)) : A1((b) - (a), (a))
#define A3(a, b) ((a) > (b)) ? A2((a) - (b), (b)) : A2((b) - (a), (a))
#define A4(a, b) ((a) > (b)) ? A3((a) - (b), (b)) : A3((b) - (a), (a))
#define A5(a, b) ((a) > (b)) ? A4((a) - (b), (b)) : A4((b) - (a), (a))
#define A6(a, b) ((a) > (b)) ? A5((a) - (b), (b)) : A5((b) - (a), (a))
#define A7(a, b) ((a) > (b)) ? A6((a) - (b), (b)) : A6((b) - (a), (a))
#define A8(a, b) ((a) > (b)) ? A7((a) - (b), (b)) : A7((b) - (a), (a))
#define A9(a, b) ((a) > (b)) ? A8((a) - (b), (b)) : A8((b) - (a), (a))
#define A10(a, b) ((a) > (b)) ? A9((a) - (b), (b)) : A9((b) - (a), (a))
#define A11(a, b) ((a) > (b)) ? A10((a) - (b), (b)) : A10((b) - (a), (a))
return A8(a, b);
}
""")
my_env = os.environ.copy()
my_env["DISABLE_VALUEFLOW"] = "1"
cppcheck([filename], env=my_env)

View File

@ -990,7 +990,7 @@ private:
check("struct S { struct T { char c; } *p; };\n" // #6541
"char f(S* s) { return s->p ? 'a' : s->p->c; }\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: p.\n",
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: s->p.\n",
errout.str());
}

View File

@ -17,6 +17,7 @@
*/
#include "errortypes.h"
#include "helpers.h"
#include "platform.h"
#include "settings.h"
#include "standards.h"
@ -236,6 +237,11 @@ private:
TEST_CASE(exprid1);
TEST_CASE(exprid2);
TEST_CASE(exprid3);
TEST_CASE(exprid4);
TEST_CASE(exprid5);
TEST_CASE(exprid6);
TEST_CASE(exprid7);
TEST_CASE(structuredBindings);
}
@ -260,9 +266,11 @@ private:
std::string tokenizeExpr_(const char* file, int line, const char code[], const char filename[] = "test.cpp") {
errout.str("");
std::vector<std::string> files(1, filename);
Tokenizer tokenizer(&settings, this);
std::istringstream istr(code);
ASSERT_LOC((tokenizer.tokenize)(istr, filename), file, line);
PreprocessorHelper::preprocess(code, files, tokenizer);
ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line);
// result..
Token::stringifyOptions options = Token::stringifyOptions::forDebugExprId();
@ -3859,9 +3867,9 @@ private:
"2: int x ; int y ;\n"
"3: } ;\n"
"4: int f ( A a , A b ) {\n"
"5: int x@5 ; x@5 =@9 a@3 .@10 x@6 +@11 b@4 .@12 x@7 ;\n"
"6: int y@8 ; y@8 =@1073741837 b@4 .@12 x@7 +@11 a@3 .@10 x@6 ;\n"
"7: return x@5 +@1073741841 y@8 +@1073741842 a@3 .@1073741843 y@9 +@1073741844 b@4 .@1073741845 y@10 ;\n"
"5: int x@5 ; x@5 =@UNIQUE a@3 .@11 x@6 +@13 b@4 .@12 x@7 ;\n"
"6: int y@8 ; y@8 =@UNIQUE b@4 .@12 x@7 +@13 a@3 .@11 x@6 ;\n"
"7: return x@5 +@UNIQUE y@8 +@UNIQUE a@3 .@UNIQUE y@9 +@UNIQUE b@4 .@UNIQUE y@10 ;\n"
"8: }\n";
ASSERT_EQUALS(expected, actual);
@ -3878,14 +3886,77 @@ private:
const char expected[] = "1: struct S { std :: unique_ptr < int > u ; } ;\n"
"2: auto f ; f = [ ] ( const S & s ) . std :: unique_ptr < int > {\n"
"3: if (@5 auto p@4 =@1073741830 s@3 .@1073741831 u@5 .@1073741832 get (@1073741833 ) ) {\n"
"4: return std ::@1073741834 make_unique < int > (@1073741835 *@1073741836 p@4 ) ; }\n"
"3: if ( auto p@4 =@UNIQUE s@3 .@UNIQUE u@5 .@UNIQUE get (@UNIQUE ) ) {\n"
"4: return std ::@UNIQUE make_unique < int > (@UNIQUE *@UNIQUE p@4 ) ; }\n"
"5: return nullptr ;\n"
"6: } ;\n";
ASSERT_EQUALS(expected, actual);
}
void exprid3() {
const char code[] = "void f(bool b, int y) {\n"
" if (b && y > 0) {}\n"
" while (b && y > 0) {}\n"
"}\n";
const char expected[] = "1: void f ( bool b , int y ) {\n"
"2: if ( b@1 &&@5 y@2 >@4 0 ) { }\n"
"3: while ( b@1 &&@5 y@2 >@4 0 ) { }\n"
"4: }\n";
ASSERT_EQUALS(expected, tokenizeExpr(code));
}
void exprid4() {
// expanded macro..
const char code[] = "#define ADD(x,y) x+y\n"
"int f(int a, int b) {\n"
" return ADD(a,b) + ADD(a,b);\n"
"}\n";
const char expected[] = "2: int f ( int a , int b ) {\n"
"3: return a@1 $+@UNIQUE b@2 +@UNIQUE a@1 $+@UNIQUE b@2 ;\n"
"4: }\n";
ASSERT_EQUALS(expected, tokenizeExpr(code));
}
void exprid5() {
// references..
const char code[] = "int foo(int a) {\n"
" int& r = a;\n"
" return (a+a)*(r+r);\n"
"}\n";
const char expected[] = "1: int foo ( int a ) {\n"
"2: int & r@2 =@UNIQUE a@1 ;\n"
"3: return ( a@1 +@4 a@1 ) *@UNIQUE ( r@2 +@4 r@2 ) ;\n"
"4: }\n";
ASSERT_EQUALS(expected, tokenizeExpr(code));
}
void exprid6() {
// ++ and -- should have UNIQUE exprid
const char code[] = "void foo(int *a) {\n"
" *a++ = 0;\n"
" if (*a++ == 32) {}\n"
"}\n";
const char expected[] = "1: void foo ( int * a ) {\n"
"2: *@UNIQUE a@1 ++@UNIQUE = 0 ;\n"
"3: if ( *@UNIQUE a@1 ++@UNIQUE ==@UNIQUE 32 ) { }\n"
"4: }\n";
ASSERT_EQUALS(expected, tokenizeExpr(code));
}
void exprid7() {
// different casts
const char code[] = "void foo(int a) {\n"
" if ((char)a == (short)a) {}\n"
" if ((char)a == (short)a) {}\n"
"}\n";
const char expected[] = "1: void foo ( int a ) {\n"
"2: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n"
"3: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n"
"4: }\n";
ASSERT_EQUALS(expected, tokenizeExpr(code));
}
void structuredBindings() {
const char code[] = "int foo() { auto [x,y] = xy(); return x+y; }";
ASSERT_EQUALS("1: int foo ( ) { auto [ x@1 , y@2 ] = xy ( ) ; return x@1 + y@2 ; }\n",