Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -71,39 +71,7 @@ }; class CallEvent; - -/// This class represents a description of a function call using the number of -/// arguments and the name of the function. -class CallDescription { - friend CallEvent; - - mutable IdentifierInfo *II = nullptr; - mutable bool IsLookupDone = false; - // The list of the qualified names used to identify the specified CallEvent, - // e.g. "{a, b}" represent the qualified names, like "a::b". - std::vector QualifiedName; - unsigned RequiredArgs; - -public: - const static unsigned NoArgRequirement = std::numeric_limits::max(); - - /// Constructs a CallDescription object. - /// - /// @param QualifiedName The list of the name qualifiers of the function that - /// will be matched. The user is allowed to skip any of the qualifiers. - /// For example, {"std", "basic_string", "c_str"} would match both - /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). - /// - /// @param RequiredArgs The number of arguments that is expected to match a - /// call. Omit this parameter to match every occurrence of call with a given - /// name regardless the number of arguments. - CallDescription(ArrayRef QualifiedName, - unsigned RequiredArgs = NoArgRequirement) - : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} - - /// Get the name of the function that this object matches. - StringRef getFunctionName() const { return QualifiedName.back(); } -}; +class CallDescription; template class CallEventRef : public IntrusiveRefCntPtr { @@ -1076,6 +1044,69 @@ } }; +/// This class represents a description of a function call using the number of +/// arguments and the name of the function. +class CallDescription { + friend CallEvent; + + mutable IdentifierInfo *II = nullptr; + mutable bool IsLookupDone = false; + // The list of the qualified names used to identify the specified CallEvent, + // e.g. "{a, b}" represent the qualified names, like "a::b". + std::vector QualifiedName; + unsigned RequiredArgs; + +public: + const static unsigned NoArgRequirement = std::numeric_limits::max(); + + /// Constructs a CallDescription object. + /// + /// @param QualifiedName The list of the name qualifiers of the function that + /// will be matched. The user is allowed to skip any of the qualifiers. + /// For example, {"std", "basic_string", "c_str"} would match both + /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). + /// + /// @param RequiredArgs The number of arguments that is expected to match a + /// call. Omit this parameter to match every occurrence of call with a given + /// name regardless the number of arguments. + CallDescription(ArrayRef QualifiedName, + unsigned RequiredArgs = NoArgRequirement) + : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} + + /// Get the name of the function that this object matches. + StringRef getFunctionName() const { return QualifiedName.back(); } +}; + +/// An immutable map from CallDescriptions to arbitrary data. Provides a unified +/// way for checkers to react on function calls. +template class CallDescriptionMap { + // Some call descriptions aren't easily hashable (eg., the ones with qualified + // names in which some sections are omitted), so let's put them + // in a simple vector and use linear lookup. + // TODO: Implement an actual map for fast lookup for "hashable" call + // descriptions (eg., the ones for C functions that just match the name). + const std::vector> LinearMap; + +public: + CallDescriptionMap( + std::initializer_list> &&List) + : LinearMap(List) {} + + // These maps are usually stored once per checker, so let's make sure + // we don't do redundant copies. + CallDescriptionMap(const CallDescriptionMap &) = delete; + + const T *lookup(const CallEvent &Call) const { + // Slow path: linear lookup. + // TODO: Implement some sort of fast path. + for (const std::pair &I : LinearMap) + if (Call.isCalled(I.first)) + return &I.second; + + return nullptr; + } +}; + /// Manages the lifetime of CallEvent objects. /// /// CallEventManager provides a way to create arbitrary CallEvents "on the Index: clang/unittests/StaticAnalyzer/CMakeLists.txt =================================================================== --- clang/unittests/StaticAnalyzer/CMakeLists.txt +++ clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_unittest(StaticAnalysisTests AnalyzerOptionsTest.cpp + CallDescriptionTest.cpp StoreTest.cpp RegisterCustomCheckersTest.cpp SymbolReaperTest.cpp Index: clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp =================================================================== --- /dev/null +++ clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @@ -0,0 +1,109 @@ +//===- 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 "Reusables.h" + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ento { +namespace { + +// Test that we can put a value into an int-type variable and load it +// back from that variable. Test what happens if default bindings are used. +class CallDescriptionConsumer : public ExprEngineConsumer { + const CallDescriptionMap &CDM; + void performTest(const Decl *D) { + using namespace ast_matchers; + + 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 bool *LookupResult = CDM.lookup(*Call); + // Check that we've found the function in the map + // with the correct description. + assert(LookupResult && *LookupResult); + } + +public: + CallDescriptionConsumer(CompilerInstance &C, + const CallDescriptionMap &CDM) + : ExprEngineConsumer(C), CDM(CDM) {} + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (const auto *D : DG) + performTest(D); + return true; + } +}; + +class CallDescriptionAction : public ASTFrontendAction { + const CallDescriptionMap &CDM; + +public: + CallDescriptionAction(const CallDescriptionMap &CDM) : CDM(CDM) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return llvm::make_unique(Compiler, CDM); + } +}; + +TEST(CallEvent, CallDescription) { + // Test simple name matching. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"bar"}, false}, + {{"foo"}, true}, + }), "void foo(); void bar() { foo(); }")); + + // Test arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", 1}, true}, + {{"foo", 2}, false}, + }), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + + // Test qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + 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();" + "}")); + + // A negative test for qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{{"foo", "bar"}}, false}, + {{"foo"}, true}, + }), "void foo(); struct bar { void foo(); }; void test() { foo(); }")); + +} + +} // namespace +} // namespace ento +} // namespace clang Index: clang/unittests/StaticAnalyzer/Reusables.h =================================================================== --- clang/unittests/StaticAnalyzer/Reusables.h +++ clang/unittests/StaticAnalyzer/Reusables.h @@ -17,16 +17,23 @@ namespace clang { namespace ento { +// Find a node in the current AST that matches a matcher. +template +const T *findNode(const Decl *Where, MatcherT What) { + using namespace ast_matchers; + auto Matches = match(decl(hasDescendant(What.bind("root"))), + *Where, Where->getASTContext()); + assert(Matches.size() <= 1 && "Ambiguous match!"); + assert(Matches.size() >= 1 && "Match not found!"); + const T *Node = selectFirst("root", Matches); + return Node; +} + // Find a declaration in the current AST by name. template const T *findDeclByName(const Decl *Where, StringRef Name) { using namespace ast_matchers; - auto Matcher = decl(hasDescendant(namedDecl(hasName(Name)).bind("d"))); - auto Matches = match(Matcher, *Where, Where->getASTContext()); - assert(Matches.size() == 1 && "Ambiguous name!"); - const T *Node = selectFirst("d", Matches); - assert(Node && "Name not found!"); - return Node; + return findNode(Where, namedDecl(hasName(Name))); } // A re-usable consumer that constructs ExprEngine out of CompilerInvocation.