163 lines
5.4 KiB
C++
163 lines
5.4 KiB
C++
|
//===- 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<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).
|
||
|
class CallDescriptionConsumer : public ExprEngineConsumer {
|
||
|
ResultMap &RM;
|
||
|
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 = 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<std::pair<CallDescription, bool>> Data)
|
||
|
: RM(Data) {}
|
||
|
|
||
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
|
||
|
StringRef File) override {
|
||
|
return std::make_unique<CallDescriptionConsumer>(Compiler, RM);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
TEST(CallEvent, CallDescription) {
|
||
|
// Test simple name matching.
|
||
|
EXPECT_TRUE(tooling::runToolOnCode(
|
||
|
std::unique_ptr<CallDescriptionAction>(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(
|
||
|
std::unique_ptr<CallDescriptionAction>(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(
|
||
|
std::unique_ptr<CallDescriptionAction>(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(
|
||
|
std::unique_ptr<CallDescriptionAction>(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(
|
||
|
std::unique_ptr<CallDescriptionAction>(new CallDescriptionAction({
|
||
|
{{{"foo", "bar"}}, false},
|
||
|
{{{"bar", "foo"}}, false},
|
||
|
{{"foo"}, true},
|
||
|
})), "void foo(); struct bar { void foo(); }; void test() { foo(); }"));
|
||
|
|
||
|
// Test CDF_MaybeBuiltin - a flag that allows matching weird builtins.
|
||
|
EXPECT_TRUE(tooling::runToolOnCode(
|
||
|
std::unique_ptr<CallDescriptionAction>(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));"
|
||
|
"}"));
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
} // namespace ento
|
||
|
} // namespace clang
|