NoQ updated this revision to Diff 201382.
NoQ added a comment.
Bring back an assertion in `findNode<>()`.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D62441/new/
https://reviews.llvm.org/D62441
Files:
clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
clang/unittests/StaticAnalyzer/CMakeLists.txt
clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp
clang/unittests/StaticAnalyzer/Reusables.h
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 <typename T, typename MatcherT>
+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<T>("root", Matches);
+ assert(Node && "Type mismatch!");
+ return Node;
+}
+
// Find a declaration in the current AST by name.
template <typename T>
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<T>("d", Matches);
- assert(Node && "Name not found!");
- return Node;
+ return findNode<T>(Where, namedDecl(hasName(Name)));
}
// A re-usable consumer that constructs ExprEngine out of CompilerInvocation.
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<bool> &CDM;
+ void performTest(const Decl *D) {
+ using namespace ast_matchers;
+
+ if (!D->hasBody())
+ return;
+
+ const CallExpr *CE = findNode<CallExpr>(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<bool> &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<bool> &CDM;
+
+public:
+ CallDescriptionAction(const CallDescriptionMap<bool> &CDM) : CDM(CDM) {}
+
+ std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
+ StringRef File) override {
+ return llvm::make_unique<CallDescriptionConsumer>(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<typename T> class basic_string {"
+ " public:"
+ " T *c_str();"
+ " };"
+ "}}"
+ "void foo() {"
+ " using namespace std;"
+ " basic_string<char> 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/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/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<const char *> QualifiedName;
- unsigned RequiredArgs;
-
-public:
- const static unsigned NoArgRequirement = std::numeric_limits<unsigned>::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<const char *> 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<typename T = CallEvent>
class CallEventRef : public IntrusiveRefCntPtr<const T> {
@@ -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<const char *> QualifiedName;
+ unsigned RequiredArgs;
+
+public:
+ const static unsigned NoArgRequirement = std::numeric_limits<unsigned>::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<const char *> 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 <typename T> 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<std::pair<CallDescription, T>> LinearMap;
+
+public:
+ CallDescriptionMap(
+ std::initializer_list<std::pair<CallDescription, T>> &&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<CallDescription, T> &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
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits