blob: 1fa73752b5292d6a197c9a78c64c57982a15d082 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/inference/collect_evidence.h"
#include <string>
#include <utility>
#include <vector>
#include "nullability/inference/inference.proto.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h" // IWYU pragma: keep
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
MATCHER_P3(isEvidenceMatcher, S, Kind, SymbolMatcher, "") {
return arg.slot() == S && arg.kind() == Kind &&
SymbolMatcher.Matches(arg.symbol());
}
testing::Matcher<const Evidence&> evidence(
Slot S, Evidence::Kind Kind,
testing::Matcher<const Symbol&> SymbolMatcher = testing::_) {
return isEvidenceMatcher(S, Kind, SymbolMatcher);
}
MATCHER_P(functionNamed, Name, "") {
return llvm::StringRef(arg.usr()).contains(
("@" + llvm::StringRef(Name) + "#").str());
}
clang::TestInputs getInputsWithAnnotationDefinitions(llvm::StringRef Source) {
clang::TestInputs Inputs = Source;
Inputs.ExtraFiles["nullability.h"] = R"cc(
template <typename T>
using Nullable [[clang::annotate("Nullable")]] = T;
template <typename T>
using Nonnull [[clang::annotate("Nonnull")]] = T;
)cc";
Inputs.ExtraArgs.push_back("-include");
Inputs.ExtraArgs.push_back("nullability.h");
return Inputs;
}
std::vector<Evidence> collectEvidenceFromTargetFunction(
llvm::StringRef Source) {
std::vector<Evidence> Results;
clang::TestAST AST(getInputsWithAnnotationDefinitions(Source));
auto Err = collectEvidenceFromImplementation(
cast<FunctionDecl>(
*dataflow::test::findValueDecl(AST.context(), "target")),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); }));
if (Err) ADD_FAILURE() << toString(std::move(Err));
return Results;
}
std::vector<Evidence> collectEvidenceFromTargetDecl(llvm::StringRef Source) {
std::vector<Evidence> Results;
clang::TestAST AST(getInputsWithAnnotationDefinitions(Source));
collectEvidenceFromTargetDeclaration(
*dataflow::test::findValueDecl(AST.context(), "target"),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); }));
return Results;
}
TEST(InferAnnotationsTest, NoParams) {
static constexpr llvm::StringRef Src = R"cc(
void target() {}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, OneParamUnused) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, OneParamUsedWithoutRestriction) {
static constexpr llvm::StringRef Src = R"cc(
void takesUnknown(int *unknown) {}
void target(int *p0) { takesUnknown(p0); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, Deref) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0, int *p1) {
int a = *p0;
if (p1 != nullptr) {
int b = *p1;
}
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, DerefOfNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void target(Nonnull<int *> p) {
*p;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, DereferenceBeforeAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p) {
*p;
int i = 1;
p = &i;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, DereferenceAfterAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p) {
int i = 1;
p = &i;
*p;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, DerefOfPtrRef) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *&p0, int *&p1) {
int a = *p0;
if (p1 != nullptr) {
int b = *p1;
}
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, UnrelatedCondition) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0, int *p1, int *p2, bool b) {
if (b) {
int a = *p0;
int b = *p1;
} else {
int a = *p0;
int c = *p2;
}
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE),
// We collect two Evidence values for two dereferences of p0
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(2), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, LaterDeref) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {
if (p0 == nullptr) {
(void)0;
} else {
(void)0;
}
int a = *p0;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, DerefBeforeGuardedDeref) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {
int a = *p0;
if (p0 != nullptr) {
int b = *p0;
}
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, EarlyReturn) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {
if (!p0) {
return;
}
int a = *p0;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, UnreachableCode) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0, int *p1, int *p2, int *p3) {
if (true) {
int a = *p0;
} else {
int a = *p1;
}
if (false) {
int a = *p2;
}
return;
int a = *p3;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(InferAnnotationsTest, VariableDeclIgnored) {
llvm::StringLiteral Src = "Nullable<int *> target;";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());
}
TEST(InferAnnotationsTest, FunctionDeclReturnType) {
llvm::StringLiteral Src = "Nonnull<int *> target();";
EXPECT_THAT(
collectEvidenceFromTargetDecl(Src),
ElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::ANNOTATED_NONNULL,
functionNamed("target"))));
}
TEST(InferAnnotationsTest, FunctionDeclParams) {
llvm::StringLiteral Src = "void target(Nullable<int*>, int*, Nonnull<int*>);";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src),
ElementsAre(evidence(paramSlot(0), Evidence::ANNOTATED_NULLABLE),
evidence(paramSlot(2), Evidence::ANNOTATED_NONNULL)));
}
TEST(InferAnnotationsTest, FunctionDeclNonTopLevel) {
llvm::StringLiteral Src = "Nonnull<int*>** target(Nullable<int*>*);";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());
}
MATCHER_P(declNamed, Name, "") {
std::string Actual;
llvm::raw_string_ostream OS(Actual);
if (auto* ND = dyn_cast<NamedDecl>(arg))
ND->getNameForDiagnostic(
OS, arg->getDeclContext()->getParentASTContext().getPrintingPolicy(),
/*Qualified=*/true);
return ::testing::ExplainMatchResult(Name, Actual, result_listener);
}
TEST(EvidenceSitesTest, Functions) {
TestAST AST(R"cc(
void foo();
void bar();
void bar() {}
void baz() {}
auto Lambda = []() {}; // Not analyzed yet.
struct S {
void member();
};
void S::member() {}
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations,
ElementsAre(declNamed("foo"), declNamed("bar"), declNamed("bar"),
declNamed("baz"), declNamed("S::member"),
declNamed("S::member")));
EXPECT_THAT(
Sites.Implementations,
ElementsAre(declNamed("bar"), declNamed("baz"), declNamed("S::member")));
}
TEST(EvidenceSitesTest, Variables) {
TestAST AST(R"cc(
int* x = true ? nullptr : nullptr;
struct S {
int* s;
};
)cc");
auto Sites = EvidenceSites::discover(AST.context());
// For now, variables are not inferrable.
EXPECT_THAT(Sites.Declarations, IsEmpty());
// For now, we don't examine variable initializers.
EXPECT_THAT(Sites.Implementations, IsEmpty());
}
TEST(EvidenceSitesTest, Templates) {
TestAST AST(R"cc(
template <int I>
int f() {
return I;
}
template <>
int f<1>() {
return 1;
}
struct S {
template <int I>
int f() {
return I;
}
};
template <int I>
struct T {
int f() { return I; }
};
auto Unused = f<0>() + f<1>() + S{}.f<0>() + T<0>{}.f();
)cc");
auto Sites = EvidenceSites::discover(AST.context());
// Relevant declarations are the written ones.
EXPECT_THAT(Sites.Declarations,
ElementsAre(declNamed("f"), declNamed("f<1>"), declNamed("S::f"),
declNamed("T::f")));
// Instantiations are relevant inference targets.
EXPECT_THAT(Sites.Implementations,
ElementsAre(declNamed("f<0>"), declNamed("f<1>"),
declNamed("S::f<0>"), declNamed("T<0>::f")));
}
} // namespace
} // namespace clang::tidy::nullability