Mercurial > hg > CbC > CbC_llvm
view clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @ 266:00f31e85ec16 default tip
Added tag current for changeset 31d058e83c98
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 14 Oct 2023 10:13:55 +0900 |
parents | 1f2b6ac9f198 |
children |
line wrap: on
line source
//===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CheckerRegistration.h" #include "Reusables.h" #include "clang/AST/ExprCXX.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" #include <type_traits> namespace clang { namespace ento { namespace { // A wrapper around CallDescriptionMap<bool> that allows verifying that // all functions have been found. This is needed because CallDescriptionMap // isn't supposed to support iteration. class ResultMap { size_t Found, Total; CallDescriptionMap<bool> Impl; public: ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data) : Found(0), Total(std::count_if(Data.begin(), Data.end(), [](const std::pair<CallDescription, bool> &Pair) { return Pair.second == true; })), Impl(std::move(Data)) {} const bool *lookup(const CallEvent &Call) { const bool *Result = Impl.lookup(Call); // If it's a function we expected to find, remember that we've found it. if (Result && *Result) ++Found; return Result; } // Fail the test if we haven't found all the true-calls we were looking for. ~ResultMap() { EXPECT_EQ(Found, Total); } }; // Scan the code body for call expressions and see if we find all calls that // 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 <typename MatchedExprT> class CallDescriptionConsumer : public ExprEngineConsumer { ResultMap &RM; void performTest(const Decl *D) { using namespace ast_matchers; using T = MatchedExprT; if (!D->hasBody()) return; const StackFrameContext *SFC = Eng.getAnalysisDeclContextManager().getStackFrame(D); const ProgramStateRef State = Eng.getInitialState(SFC); // FIXME: Maybe use std::variant and std::visit for these. const auto MatcherCreator = []() { if (std::is_same<T, CallExpr>::value) return callExpr(); if (std::is_same<T, CXXConstructExpr>::value) return cxxConstructExpr(); if (std::is_same<T, CXXMemberCallExpr>::value) return cxxMemberCallExpr(); if (std::is_same<T, CXXOperatorCallExpr>::value) return cxxOperatorCallExpr(); llvm_unreachable("Only these expressions are supported for now."); }; const Expr *E = findNode<T>(D, MatcherCreator()); CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager(); CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> { CFGBlock::ConstCFGElementRef ElemRef = {SFC->getCallSiteBlock(), SFC->getIndex()}; if (std::is_base_of<CallExpr, T>::value) return CEMgr.getCall(E, State, SFC, ElemRef); if (std::is_same<T, CXXConstructExpr>::value) return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E), /*Target=*/nullptr, State, SFC, ElemRef); llvm_unreachable("Only these expressions are supported for now."); }(); // If the call actually matched, check if we really expected it to match. const bool *LookupResult = RM.lookup(*Call); EXPECT_TRUE(!LookupResult || *LookupResult); // ResultMap is responsible for making sure that we've found *all* calls. } public: CallDescriptionConsumer(CompilerInstance &C, ResultMap &RM) : ExprEngineConsumer(C), RM(RM) {} bool HandleTopLevelDecl(DeclGroupRef DG) override { for (const auto *D : DG) performTest(D); return true; } }; template <typename MatchedExprT = CallExpr> class CallDescriptionAction : public ASTFrontendAction { ResultMap RM; public: CallDescriptionAction( std::initializer_list<std::pair<CallDescription, bool>> Data) : RM(Data) {} std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, StringRef File) override { return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler, RM); } }; TEST(CallDescription, SimpleNameMatching) { EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(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(); }")); } TEST(CallDescription, RequiredArguments) { EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"foo"}, 1}, true}, {{{"foo"}, 2}, false}, })), "void foo(int); void foo(int, int); void bar() { foo(1); }")); } TEST(CallDescription, LackOfRequiredArguments) { EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"foo"}, std::nullopt}, true}, {{{"foo"}, 2}, false}, })), "void foo(int); void foo(int, int); void bar() { foo(1); }")); } constexpr StringRef MockStdStringHeader = R"code( namespace std { inline namespace __1 { template<typename T> 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<char>; } // namespace std )code"; TEST(CallDescription, QualifiedNames) { constexpr StringRef AdditionalCode = R"code( void foo() { using namespace std; basic_string<char> s; s.c_str(); })code"; const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "basic_string", "c_str"}}, true}, })), Code)); } TEST(CallDescription, MatchConstructor) { constexpr StringRef AdditionalCode = R"code( void foo() { using namespace std; basic_string<char> s("hello"); })code"; const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str(); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>( new CallDescriptionAction<CXXConstructExpr>({ {{{"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 Bar { operator int(); }; } // bbb } // aaa void foo() { aaa::bbb::Bar x; int tmp = x; })code"; EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"aaa", "bbb", "Bar", "operator int"}}, true}, })), Code)); } TEST(CallDescription, RejectOverQualifiedNames) { constexpr auto Code = R"code( namespace my { namespace std { struct container { const char *data() const; }; } // namespace std } // namespace my void foo() { using namespace my; std::container v; v.data(); })code"; // FIXME: We should **not** match. EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "container", "data"}}, true}, })), Code)); } TEST(CallDescription, DontSkipNonInlineNamespaces) { constexpr auto Code = R"code( namespace my { /*not inline*/ namespace v1 { void bar(); } // namespace v1 } // namespace my void foo() { my::v1::bar(); })code"; { SCOPED_TRACE("my v1 bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"my", "v1", "bar"}}, true}, })), Code)); } { // FIXME: We should **not** skip non-inline namespaces. SCOPED_TRACE("my bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"my", "bar"}}, true}, })), Code)); } } TEST(CallDescription, SkipTopInlineNamespaces) { constexpr auto Code = R"code( inline namespace my { namespace v1 { void bar(); } // namespace v1 } // namespace my void foo() { using namespace v1; bar(); })code"; { SCOPED_TRACE("my v1 bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"my", "v1", "bar"}}, true}, })), Code)); } { SCOPED_TRACE("v1 bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"v1", "bar"}}, true}, })), Code)); } } TEST(CallDescription, SkipAnonimousNamespaces) { constexpr auto Code = R"code( namespace { namespace std { namespace { inline namespace { struct container { const char *data() const { return nullptr; }; }; } // namespace inline anonymous } // namespace anonymous } // namespace std } // namespace anonymous void foo() { std::container v; v.data(); })code"; EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "container", "data"}}, true}, })), Code)); } TEST(CallDescription, AliasNames) { constexpr StringRef AliasNamesCode = R"code( namespace std { struct container { const char *data() const; }; using cont = container; } // std )code"; constexpr StringRef UseAliasInSpelling = R"code( void foo() { std::cont v; v.data(); })code"; constexpr StringRef UseStructNameInSpelling = R"code( void foo() { std::container v; v.data(); })code"; const std::string UseAliasInSpellingCode = (Twine{AliasNamesCode} + UseAliasInSpelling).str(); const std::string UseStructNameInSpellingCode = (Twine{AliasNamesCode} + UseStructNameInSpelling).str(); // Test if the code spells the alias, wile we match against the struct name, // and again matching against the alias. { SCOPED_TRACE("Using alias in spelling"); { SCOPED_TRACE("std container data"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "container", "data"}}, true}, })), UseAliasInSpellingCode)); } { // FIXME: We should be able to see-through aliases. SCOPED_TRACE("std cont data"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "cont", "data"}}, false}, })), UseAliasInSpellingCode)); } } // Test if the code spells the struct name, wile we match against the struct // name, and again matching against the alias. { SCOPED_TRACE("Using struct name in spelling"); { SCOPED_TRACE("std container data"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "container", "data"}}, true}, })), UseAliasInSpellingCode)); } { // FIXME: We should be able to see-through aliases. SCOPED_TRACE("std cont data"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"std", "cont", "data"}}, false}, })), UseAliasInSpellingCode)); } } } TEST(CallDescription, AliasSingleNamespace) { constexpr StringRef Code = R"code( namespace aaa { namespace bbb { namespace ccc { void bar(); }} // namespace bbb::ccc namespace bbb_alias = bbb; } // namespace aaa void foo() { aaa::bbb_alias::ccc::bar(); })code"; { SCOPED_TRACE("aaa bbb ccc bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"aaa", "bbb", "ccc", "bar"}}, true}, })), Code)); } { // FIXME: We should be able to see-through namespace aliases. SCOPED_TRACE("aaa bbb_alias ccc bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"aaa", "bbb_alias", "ccc", "bar"}}, false}, })), Code)); } } TEST(CallDescription, AliasMultipleNamespaces) { constexpr StringRef Code = R"code( namespace aaa { namespace bbb { namespace ccc { void bar(); }}} // namespace aaa::bbb::ccc namespace aaa_bbb_ccc = aaa::bbb::ccc; void foo() { using namespace aaa_bbb_ccc; bar(); })code"; { SCOPED_TRACE("aaa bbb ccc bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"aaa", "bbb", "ccc", "bar"}}, true}, })), Code)); } { // FIXME: We should be able to see-through namespace aliases. SCOPED_TRACE("aaa_bbb_ccc bar"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"aaa_bbb_ccc", "bar"}}, false}, })), Code)); } } TEST(CallDescription, NegativeMatchQualifiedNames) { EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({ {{{"foo", "bar"}}, false}, {{{"bar", "foo"}}, false}, {{{"foo"}}, true}, })), "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<FrontendAction>(new CallDescriptionAction<>( {{{{"memset"}, 3}, false}, {{CDF_MaybeBuiltin, {"memset"}, 3}, true}})), "void foo() {" " int x;" " __builtin___memset_chk(&x, 0, sizeof(x)," " __builtin_object_size(&x, 0));" "}")); { SCOPED_TRACE("multiple similar builtins"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( {{{CDF_MaybeBuiltin, {"memcpy"}, 3}, false}, {{CDF_MaybeBuiltin, {"wmemcpy"}, 3}, true}})), R"(void foo(wchar_t *x, wchar_t *y) { __builtin_wmemcpy(x, y, sizeof(wchar_t)); })")); } { SCOPED_TRACE("multiple similar builtins reversed order"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( {{{CDF_MaybeBuiltin, {"wmemcpy"}, 3}, true}, {{CDF_MaybeBuiltin, {"memcpy"}, 3}, false}})), R"(void foo(wchar_t *x, wchar_t *y) { __builtin_wmemcpy(x, y, sizeof(wchar_t)); })")); } { SCOPED_TRACE("lookbehind and lookahead mismatches"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( {{{CDF_MaybeBuiltin, {"func"}}, false}})), R"( void funcXXX(); void XXXfunc(); void XXXfuncXXX(); void test() { funcXXX(); XXXfunc(); XXXfuncXXX(); })")); } { SCOPED_TRACE("lookbehind and lookahead matches"); EXPECT_TRUE(tooling::runToolOnCode( std::unique_ptr<FrontendAction>(new CallDescriptionAction<>( {{{CDF_MaybeBuiltin, {"func"}}, true}})), R"( void func(); void func_XXX(); void XXX_func(); void XXX_func_XXX(); void test() { func(); // exact match func_XXX(); XXX_func(); XXX_func_XXX(); })")); } } //===----------------------------------------------------------------------===// // Testing through a checker interface. // // Above, the static analyzer isn't run properly, only the bare minimum to // create CallEvents. This causes CallEvents through function pointers to not // refer to the pointee function, but this works fine if we run // AnalysisASTConsumer. //===----------------------------------------------------------------------===// class CallDescChecker : public Checker<check::PreCall, check::PreStmt<CallExpr>> { CallDescriptionSet Set = {{{"bar"}, 0}}; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (Set.contains(Call)) { C.getBugReporter().EmitBasicReport( Call.getDecl(), this, "CallEvent match", categories::LogicError, "CallEvent match", PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()}); } } void checkPreStmt(const CallExpr *CE, CheckerContext &C) const { if (Set.containsAsWritten(*CE)) { C.getBugReporter().EmitBasicReport( CE->getCalleeDecl(), this, "CallExpr match", categories::LogicError, "CallExpr match", PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()}); } } }; void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer, AnalyzerOptions &AnOpts) { AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}}; AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { Registry.addChecker<CallDescChecker>("test.CallDescChecker", "Description", ""); }); } TEST(CallDescription, CheckCallExprMatching) { // Imprecise matching shouldn't catch the call to bar, because its obscured // by a function pointer. constexpr StringRef FnPtrCode = R"code( void bar(); void foo() { void (*fnptr)() = bar; fnptr(); })code"; std::string Diags; EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags, /*OnlyEmitWarnings*/ true)); EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags); // This should be caught properly by imprecise matching, as the call is done // purely through syntactic means. constexpr StringRef Code = R"code( void bar(); void foo() { bar(); })code"; Diags.clear(); EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags, /*OnlyEmitWarnings*/ true)); EXPECT_EQ("test.CallDescChecker: CallEvent match\n" "test.CallDescChecker: CallExpr match\n", Diags); } } // namespace } // namespace ento } // namespace clang