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,70 @@ } }; +/// 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; + Optional RequiredArgs; + +public: + /// 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, + Optional RequiredArgs = None) + : 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). + std::vector> LinearMap; + +public: + CallDescriptionMap( + std::initializer_list> &&List) + : LinearMap(List) {} + + ~CallDescriptionMap() = default; + + // These maps are usually stored once per checker, so let's make sure + // we don't do redundant copies. + CallDescriptionMap(const CallDescriptionMap &) = delete; + CallDescriptionMap &operator=(const CallDescription &) = 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/lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -393,8 +393,7 @@ return false; } - return (CD.RequiredArgs == CallDescription::NoArgRequirement || - CD.RequiredArgs == getNumArgs()); + return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()); } SVal CallEvent::getArgSVal(unsigned Index) const { 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,150 @@ +//===- 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 { + +// A wrapper around CallDescriptionMap 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 Impl; + +public: + ResultMap(std::initializer_list> Data) + : Found(0), + Total(std::count_if(Data.begin(), Data.end(), + [](const std::pair &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). +class CallDescriptionConsumer : public ExprEngineConsumer { + ResultMap &RM; + 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 = RM.lookup(*Call); + // Check that we've found the function in the map + // with the correct description. + 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; + } +}; + +class CallDescriptionAction : public ASTFrontendAction { + ResultMap RM; + +public: + CallDescriptionAction( + std::initializer_list> Data) + : RM(Data) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return llvm::make_unique(Compiler, RM); + } +}; + +TEST(CallEvent, CallDescription) { + // Test simple name matching. + EXPECT_TRUE(tooling::runToolOnCode( + 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 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 lack of arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", None}, 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}, + {{{"bar", "foo"}}, 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,24 @@ 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); + assert(Node && "Type mismatch!"); + 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.