Skip to content

Commit d95f754

Browse files
authored
Evaluate type traits in valueflow (#7393)
Right now it just evaluates `is_void`, `is_lvalue_reference`, and `is_rvalue_reference`. More work needed to handle library types in the type traits
1 parent 5ac33c8 commit d95f754

File tree

2 files changed

+319
-0
lines changed

2 files changed

+319
-0
lines changed

lib/valueflow.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,178 @@ static void valueFlowString(TokenList& tokenlist, const Settings& settings)
592592
}
593593
}
594594

595+
static const Token* findTypeEnd(const Token* tok)
596+
{
597+
while (Token::Match(tok, "%name%|::|<|(|*|&|&&") && !tok->isVariable()) {
598+
if (tok->link())
599+
tok = tok->link();
600+
tok = tok->next();
601+
}
602+
return tok;
603+
}
604+
605+
static std::vector<const Token*> evaluateType(const Token* start, const Token* end)
606+
{
607+
std::vector<const Token*> result;
608+
if (!start)
609+
return result;
610+
if (!end)
611+
return result;
612+
for (const Token* tok = start; tok != end; tok = tok->next()) {
613+
if (!Token::Match(tok, "%name%|::|<|(|*|&|&&"))
614+
return {};
615+
if (Token::simpleMatch(tok, "decltype (")) {
616+
if (Token::Match(tok->next(), "( %name% )")) {
617+
const Token* vartok = tok->tokAt(2);
618+
if (vartok->function() && !vartok->variable()) {
619+
result.push_back(vartok);
620+
} else {
621+
auto t = Token::typeDecl(vartok);
622+
if (!t.first || !t.second)
623+
return {};
624+
auto inner = evaluateType(t.first, t.second);
625+
if (inner.empty())
626+
return {};
627+
result.insert(result.end(), inner.begin(), inner.end());
628+
}
629+
} else if (Token::Match(tok->next(), "( %name% (|{") && Token::Match(tok->linkAt(3), "}|) )")) {
630+
const Token* ftok = tok->tokAt(3);
631+
auto t = Token::typeDecl(ftok);
632+
if (!t.first || !t.second)
633+
return {};
634+
auto inner = evaluateType(t.first, t.second);
635+
if (inner.empty())
636+
return {};
637+
result.insert(result.end(), inner.begin(), inner.end());
638+
} else {
639+
// We cant evaluate the decltype so bail
640+
return {};
641+
}
642+
tok = tok->linkAt(1);
643+
644+
} else {
645+
if (tok->link()) {
646+
auto inner = evaluateType(tok->next(), tok->link());
647+
if (inner.empty())
648+
return {};
649+
result.push_back(tok);
650+
result.insert(result.end(), inner.begin(), inner.end());
651+
tok = tok->link();
652+
}
653+
result.push_back(tok);
654+
}
655+
}
656+
return result;
657+
}
658+
659+
static bool hasUnknownType(const std::vector<const Token*>& toks)
660+
{
661+
if (toks.empty())
662+
return true;
663+
return std::any_of(toks.begin(), toks.end(), [&](const Token* tok) {
664+
if (!tok)
665+
return true;
666+
if (Token::Match(tok, "const|volatile|&|*|&&|%type%|::"))
667+
return false;
668+
if (Token::Match(tok, "%name% :: %name%"))
669+
return false;
670+
if (tok->type())
671+
return false;
672+
return true;
673+
});
674+
}
675+
676+
static void stripCV(std::vector<const Token*>& toks)
677+
{
678+
auto it =
679+
std::find_if(toks.begin(), toks.end(), [&](const Token* tok) {
680+
return !Token::Match(tok, "const|volatile");
681+
});
682+
if (it == toks.begin())
683+
return;
684+
toks.erase(toks.begin(), it);
685+
}
686+
687+
static std::vector<std::vector<const Token*>> evaluateTemplateArgs(const Token* tok)
688+
{
689+
std::vector<std::vector<const Token*>> result;
690+
while (tok) {
691+
const Token* next = tok->nextTemplateArgument();
692+
const Token* end = next ? next->previous() : findTypeEnd(tok);
693+
result.push_back(evaluateType(tok, end));
694+
tok = next;
695+
}
696+
return result;
697+
}
698+
699+
static void valueFlowTypeTraits(TokenList& tokenlist, const Settings& settings)
700+
{
701+
std::unordered_map<std::string, std::function<ValueFlow::Value(std::vector<std::vector<const Token*>> args)>> eval;
702+
eval["is_void"] = [&](std::vector<std::vector<const Token*>> args) {
703+
if (args.size() != 1)
704+
return ValueFlow::Value::unknown();
705+
stripCV(args[0]);
706+
if (args[0].size() == 1 && args[0][0]->str() == "void")
707+
return ValueFlow::Value(1);
708+
if (!hasUnknownType(args[0]))
709+
return ValueFlow::Value(0);
710+
return ValueFlow::Value::unknown();
711+
};
712+
eval["is_lvalue_reference"] = [&](const std::vector<std::vector<const Token*>>& args) {
713+
if (args.size() != 1)
714+
return ValueFlow::Value::unknown();
715+
if (args[0].back()->str() == "&")
716+
return ValueFlow::Value(1);
717+
if (!hasUnknownType(args[0]))
718+
return ValueFlow::Value(0);
719+
return ValueFlow::Value::unknown();
720+
};
721+
eval["is_rvalue_reference"] = [&](const std::vector<std::vector<const Token*>>& args) {
722+
if (args.size() != 1)
723+
return ValueFlow::Value::unknown();
724+
if (args[0].back()->str() == "&&")
725+
return ValueFlow::Value(1);
726+
if (!hasUnknownType(args[0]))
727+
return ValueFlow::Value(0);
728+
return ValueFlow::Value::unknown();
729+
};
730+
eval["is_reference"] = [&](const std::vector<std::vector<const Token*>>& args) {
731+
if (args.size() != 1)
732+
return ValueFlow::Value::unknown();
733+
ValueFlow::Value isRValue = eval["is_rvalue_reference"](args);
734+
if (isRValue.isUninitValue() || isRValue.intvalue == 1)
735+
return isRValue;
736+
return eval["is_lvalue_reference"](args);
737+
};
738+
for (Token* tok = tokenlist.front(); tok; tok = tok->next()) {
739+
740+
if (!Token::Match(tok, "std :: %name% <"))
741+
continue;
742+
Token* templateTok = tok->tokAt(3);
743+
Token* updateTok = nullptr;
744+
std::string traitName = tok->strAt(2);
745+
if (endsWith(traitName, "_v")) {
746+
traitName.pop_back();
747+
traitName.pop_back();
748+
updateTok = tok->next();
749+
} else if (Token::simpleMatch(templateTok->link(), "> { }") ||
750+
Token::simpleMatch(templateTok->link(), "> :: value")) {
751+
updateTok = templateTok->link()->next();
752+
}
753+
if (!updateTok)
754+
continue;
755+
if (updateTok->hasKnownIntValue())
756+
continue;
757+
if (eval.count(traitName) == 0)
758+
continue;
759+
ValueFlow::Value value = eval[traitName](evaluateTemplateArgs(templateTok->next()));
760+
if (value.isUninitValue())
761+
continue;
762+
value.setKnown();
763+
ValueFlow::setTokenValue(updateTok, std::move(value), settings);
764+
}
765+
}
766+
595767
static void valueFlowArray(TokenList& tokenlist, const Settings& settings)
596768
{
597769
std::map<nonneg int, const Token*> constantArrays;
@@ -7254,6 +7426,7 @@ void ValueFlow::setValues(TokenList& tokenlist,
72547426
VFA(valueFlowEnumValue(symboldatabase, settings)),
72557427
VFA(valueFlowNumber(tokenlist, settings)),
72567428
VFA(valueFlowString(tokenlist, settings)),
7429+
VFA(valueFlowTypeTraits(tokenlist, settings)),
72577430
VFA(valueFlowArray(tokenlist, settings)),
72587431
VFA(valueFlowUnknownFunctionReturn(tokenlist, settings)),
72597432
VFA(valueFlowGlobalConstVar(tokenlist, settings)),

test/testvalueflow.cpp

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class TestValueFlow : public TestFixture {
5757

5858
TEST_CASE(valueFlowNumber);
5959
TEST_CASE(valueFlowString);
60+
TEST_CASE(valueFlowTypeTraits);
6061
TEST_CASE(valueFlowPointerAlias);
6162
TEST_CASE(valueFlowLifetime);
6263
TEST_CASE(valueFlowArrayElement);
@@ -532,6 +533,21 @@ class TestValueFlow : public TestFixture {
532533
return values.size() == 1U && !values.front().isTokValue() ? values.front() : ValueFlow::Value();
533534
}
534535

536+
#define testKnownValueOfTok(...) testKnownValueOfTok_(__FILE__, __LINE__, __VA_ARGS__)
537+
bool testKnownValueOfTok_(const char* file,
538+
int line,
539+
const char code[],
540+
const char tokstr[],
541+
int value,
542+
const Settings* s = nullptr,
543+
bool cpp = true)
544+
{
545+
std::list<ValueFlow::Value> values = removeImpossible(tokenValues_(file, line, code, tokstr, s, cpp));
546+
return std::any_of(values.begin(), values.end(), [&](const ValueFlow::Value& v) {
547+
return v.isKnown() && v.isIntValue() && v.intvalue == value;
548+
});
549+
}
550+
535551
static std::list<ValueFlow::Value> removeSymbolicTok(std::list<ValueFlow::Value> values)
536552
{
537553
values.remove_if([](const ValueFlow::Value& v) {
@@ -596,6 +612,136 @@ class TestValueFlow : public TestFixture {
596612
ASSERT_EQUALS(true, testValueOfX(code, 2, "\"abc\"", ValueFlow::Value::ValueType::TOK));
597613
}
598614

615+
void valueFlowTypeTraits()
616+
{
617+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<void>{};", "{", 1));
618+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<void>::value;", ":: value", 1));
619+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void_v<void>;", "::", 1));
620+
621+
// is_void
622+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<int>{};", "{", 0));
623+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<void*>{};", "{", 0));
624+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<const void>{};", "{", 1));
625+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_void<volatile void>{};", "{", 1));
626+
627+
// is_lvalue_reference
628+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_lvalue_reference<int>{};", "{", 0));
629+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_lvalue_reference<int&>{};", "{", 1));
630+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_lvalue_reference<int&&>{};", "{", 0));
631+
632+
// is_rvalue_reference
633+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_rvalue_reference<int>{};", "{", 0));
634+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_rvalue_reference<int&>{};", "{", 0));
635+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_rvalue_reference<int&&>{};", "{", 1));
636+
637+
// is_reference
638+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_reference<int>{};", "{", 0));
639+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_reference<int&>{};", "{", 1));
640+
ASSERT_EQUALS(true, testKnownValueOfTok("std::is_reference<int&&>{};", "{", 1));
641+
642+
{
643+
const char* code;
644+
code = "void bar();\n"
645+
"void foo() { std::is_void<decltype(bar())>::value; }";
646+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
647+
648+
code = "int bar();\n"
649+
"void foo() { std::is_void<decltype(bar())>::value; }";
650+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
651+
652+
code = "void bar();\n"
653+
"void foo() { std::is_void<decltype(bar)>::value; }";
654+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
655+
656+
code = "class A;\n"
657+
"void foo() { std::is_lvalue_reference<A>::value; }";
658+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
659+
660+
code = "class A;\n"
661+
"void foo() { std::is_lvalue_reference<A&>::value; }";
662+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
663+
664+
code = "class A;\n"
665+
"void foo() { std::is_lvalue_reference<A&&>::value; }";
666+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
667+
668+
code = "class A;\n"
669+
"void foo() { std::is_rvalue_reference<A>::value; }";
670+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
671+
672+
code = "class A;\n"
673+
"void foo() { std::is_rvalue_reference<A&>::value; }";
674+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
675+
676+
code = "class A;\n"
677+
"void foo() { std::is_rvalue_reference<A&&>::value; }";
678+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
679+
680+
code = "class A;\n"
681+
"void foo() { std::is_reference<A>::value; }";
682+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
683+
684+
code = "class A;\n"
685+
"void foo() { std::is_reference<A&>::value; }";
686+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
687+
688+
code = "class A;\n"
689+
"void foo() { std::is_reference<A&&>::value; }";
690+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
691+
692+
code = "void foo() {\n"
693+
" int bar;\n"
694+
" std::is_void<decltype(bar)>::value;\n"
695+
"}\n";
696+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
697+
698+
code = "void foo(int bar) {\n"
699+
" std::is_lvalue_reference<decltype(bar)>::value;\n"
700+
"}\n";
701+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
702+
703+
code = "void foo(int& bar) {\n"
704+
" std::is_lvalue_reference<decltype(bar)>::value;\n"
705+
"}\n";
706+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
707+
708+
code = "void foo(int&& bar) {\n"
709+
" std::is_lvalue_reference<decltype(bar)>::value;\n"
710+
"}\n";
711+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
712+
713+
code = "void foo(int bar) {\n"
714+
" std::is_rvalue_reference<decltype(bar)>::value;\n"
715+
"}\n";
716+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
717+
718+
code = "void foo(int& bar) {\n"
719+
" std::is_rvalue_reference<decltype(bar)>::value;\n"
720+
"}\n";
721+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
722+
723+
code = "void foo(int&& bar) {\n"
724+
" std::is_rvalue_reference<decltype(bar)>::value;\n"
725+
"}\n";
726+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
727+
728+
code = "void foo(int bar) {\n"
729+
" std::is_reference<decltype(bar)>::value;\n"
730+
"}\n";
731+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 0));
732+
733+
code = "void foo(int& bar) {\n"
734+
" std::is_reference<decltype(bar)>::value;\n"
735+
"}\n";
736+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
737+
738+
code = "void foo(int&& bar) {\n"
739+
" std::is_reference<decltype(bar)>::value;\n"
740+
"}\n";
741+
ASSERT_EQUALS(true, testKnownValueOfTok(code, ":: value", 1));
742+
}
743+
}
744+
599745
void valueFlowPointerAlias() {
600746
const char *code;
601747
std::list<ValueFlow::Value> values;

0 commit comments

Comments
 (0)