blob: 98c8eb27516f66e365a620ed0d1eb2ef1885ea1f [file] [log] [blame]
//===- 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/AST/ExprCXX.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.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> {
if (std::is_base_of<CallExpr, T>::value)
return CEMgr.getCall(E, State, SFC);
if (std::is_same<T, CXXConstructExpr>::value)
return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E),
/*Target=*/nullptr, State, SFC);
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", None}, 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));"
"}"));
}
} // namespace
} // namespace ento
} // namespace clang