Index: clang/lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -328,9 +328,8 @@ if (const auto *II = Name.getAsIdentifierInfo()) return II == CD.II; // Fast case. - // If it's not a simple identifier, e.g. C++ overload, constructor, operator - // call, simply report mismatch. - return false; + // Fallback to the slow case. + return Name.getAsString() == CD.QualifiedName.back(); }; const auto ExactMatchArgAndParamCounts = Index: clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp =================================================================== --- clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp +++ clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @@ -11,6 +11,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" +#include namespace clang { namespace ento { @@ -48,7 +49,9 @@ // we were supposed to find ("true" in the provided ResultMap) and that we // don't find the ones that we weren't supposed to find // ("false" in the ResultMap). +template class CallDescriptionConsumer : public ExprEngineConsumer { + using T = MatchedExprT; ResultMap &RM; void performTest(const Decl *D) { using namespace ast_matchers; @@ -56,12 +59,34 @@ if (!D->hasBody()) return; - const CallExpr *CE = findNode(D, callExpr()); const StackFrameContext *SFC = Eng.getAnalysisDeclContextManager().getStackFrame(D); - ProgramStateRef State = Eng.getInitialState(SFC); - CallEventRef<> Call = - Eng.getStateManager().getCallEventManager().getCall(CE, State, SFC); + const ProgramStateRef State = Eng.getInitialState(SFC); + + // FIXME: Maybe use std::variant and std::visit for these. + const auto MatcherCreator = []() { + if (std::is_same::value) + return callExpr(); + if (std::is_same::value) + return cxxConstructExpr(); + if (std::is_same::value) + return cxxMemberCallExpr(); + if (std::is_same::value) + return cxxOperatorCallExpr(); + llvm_unreachable("Only these expressions are supported for now."); + }; + + const Expr *E = findNode(D, MatcherCreator()); + + CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager(); + CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef { + if (std::is_base_of::value) + return CEMgr.getCall(E, State, SFC); + if (std::is_same::value) + return CEMgr.getCXXConstructorCall(cast(E), + /*Target=*/nullptr, State, SFC); + llvm_unreachable("Only these expressions are supported for now."); + }(); const bool *LookupResult = RM.lookup(*Call); // Check that we've found the function in the map @@ -83,6 +108,7 @@ } }; +template class CallDescriptionAction : public ASTFrontendAction { ResultMap RM; @@ -93,63 +119,125 @@ std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef File) override { - return std::make_unique(Compiler, RM); + return std::make_unique>(Compiler, + RM); } }; -TEST(CallEvent, CallDescription) { - // Test simple name matching. +TEST(CallDescription, SimpleNameMatching) { EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ + std::unique_ptr(new CallDescriptionAction<>({ {{"bar"}, false}, // false: there's no call to 'bar' in this code. {{"foo"}, true}, // true: there's a call to 'foo' in this code. - })), "void foo(); void bar() { foo(); }")); + })), + "void foo(); void bar() { foo(); }")); +} - // Test arguments check. +TEST(CallDescription, RequiredArguments) { EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ + std::unique_ptr(new CallDescriptionAction<>({ {{"foo", 1}, true}, {{"foo", 2}, false}, - })), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + })), + "void foo(int); void foo(int, int); void bar() { foo(1); }")); +} - // Test lack of arguments check. +TEST(CallDescription, LackOfRequiredArguments) { EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ + std::unique_ptr(new CallDescriptionAction<>({ {{"foo", None}, true}, {{"foo", 2}, false}, - })), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + })), + "void foo(int); void foo(int, int); void bar() { foo(1); }")); +} - // Test qualified names. +constexpr StringRef MockStdStringHeader = R"code( + namespace std { inline namespace __1 { + template class basic_string { + class Allocator {}; + public: + basic_string(); + explicit basic_string(const char*, const Allocator & = Allocator()); + ~basic_string(); + T *c_str(); + }; + } // namespace __1 + using string = __1::basic_string; + } // namespace std +)code"; + +TEST(CallDescription, QualifiedNames) { + constexpr StringRef AdditionalCode = R"code( + void foo() { + using namespace std; + basic_string s; + s.c_str(); + })code"; + const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ + std::unique_ptr(new CallDescriptionAction<>({ {{{"std", "basic_string", "c_str"}}, true}, })), - "namespace std { inline namespace __1 {" - " template class basic_string {" - " public:" - " T *c_str();" - " };" - "}}" - "void foo() {" - " using namespace std;" - " basic_string s;" - " s.c_str();" - "}")); + Code)); +} - // A negative test for qualified names. +TEST(CallDescription, MatchConstructor) { + constexpr StringRef AdditionalCode = R"code( + void foo() { + using namespace std; + basic_string s("hello"); + })code"; + const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ + std::unique_ptr( + new CallDescriptionAction({ + {{{"std", "basic_string", "basic_string"}, 2, 2}, true}, + })), + Code)); +} + +// FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"} +// This feature is actually implemented, but the test infra is not yet +// sophisticated enough for testing this. To do that, we will need to +// implement a much more advanced dispatching mechanism using the CFG for +// the implicit destructor events. + +TEST(CallDescription, MatchConversionOperator) { + constexpr StringRef Code = R"code( + namespace aaa { + namespace bbb { + struct Apple { + operator int(); + }; + } // bbb + } // aaa + void foo() { + aaa::bbb::Apple x; + int tmp = x; + })code"; + EXPECT_TRUE( + tooling::runToolOnCode(std::unique_ptr( + new CallDescriptionAction({ + {{{"aaa", "bbb", "operator int"}}, true}, + })), + Code)); +} + +TEST(CallDescription, NegativeMatchQualifiedNames) { + EXPECT_TRUE(tooling::runToolOnCode( + std::unique_ptr(new CallDescriptionAction<>({ {{{"foo", "bar"}}, false}, {{{"bar", "foo"}}, false}, {{"foo"}, true}, - })), "void foo(); struct bar { void foo(); }; void test() { foo(); }")); + })), + "void foo(); struct bar { void foo(); }; void test() { foo(); }")); +} +TEST(CallDescription, MatchBuiltins) { // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins. EXPECT_TRUE(tooling::runToolOnCode( - std::unique_ptr(new CallDescriptionAction({ - {{"memset", 3}, false}, - {{CDF_MaybeBuiltin, "memset", 3}, true} - })), + std::unique_ptr(new CallDescriptionAction<>( + {{{"memset", 3}, false}, {{CDF_MaybeBuiltin, "memset", 3}, true}})), "void foo() {" " int x;" " __builtin___memset_chk(&x, 0, sizeof(x),"