blob: 696a7cd1320d87500ed3d15fff3566c49af922cf [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/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.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 ::clang::ast_matchers::functionDecl;
using ::clang::ast_matchers::hasName;
using ::clang::ast_matchers::isTemplateInstantiation;
using ::clang::ast_matchers::match;
using ::testing::_;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
MATCHER_P3(isEvidenceMatcher, SlotMatcher, KindMatcher, SymbolMatcher, "") {
return SlotMatcher.Matches(static_cast<Slot>(arg.slot())) &&
KindMatcher.Matches(arg.kind()) && SymbolMatcher.Matches(arg.symbol());
}
testing::Matcher<const Evidence&> evidence(
testing::Matcher<Slot> S, testing::Matcher<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(CollectEvidenceFromImplementationTest, NoParams) {
static constexpr llvm::StringRef Src = R"cc(
void target() {}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(CollectEvidenceFromImplementationTest, OneParamUnused) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(CollectEvidenceFromImplementationTest, OneParamUsedWithoutRestriction) {
static constexpr llvm::StringRef Src = R"cc(
void takesUnknown(int *unknown) {}
void target(int *p0) { takesUnknown(p0); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromImplementationTest, 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, Location) {
llvm::StringRef Code = "void target(int *p) { *p; }";
// 12345678901234567890123456
// 0 1 2
auto Evidence = collectEvidenceFromTargetFunction(Code);
ASSERT_THAT(Evidence, ElementsAre(evidence(paramSlot(0),
Evidence::UNCHECKED_DEREFERENCE)));
EXPECT_EQ("input.mm:1:23", Evidence.front().location());
}
TEST(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, 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(CollectEvidenceFromImplementationTest, NullableArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(Nullable<int *> p) { callee(p); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, NonnullArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(Nonnull<int *> p) { callee(p); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, UnknownArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(int *p) { callee(p); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, CheckedArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(int *p) {
if (p) callee(p);
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, NullptrPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int* q);
void target() {
callee(nullptr);
int* p = nullptr;
callee(nullptr);
}
)cc";
EXPECT_THAT(
collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee")),
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, NonPtrArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int q);
void target(int p) { callee(p); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
TEST(CollectEvidenceFromImplementationTest, NullableReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target() { return nullptr; }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromImplementationTest, NonnullReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(Nonnull<int*> p) {
return p;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromImplementationTest, UnknownReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p) { return p; }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromImplementationTest, MultipleReturns) {
static constexpr llvm::StringRef Src = R"cc(
int* target(Nonnull<int*> p, Nullable<int*> q, bool b, bool c) {
if (b) return q;
if (c) return nullptr;
return p;
}
)cc";
EXPECT_THAT(
collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target")),
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target")),
evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromImplementationTest, MemberOperatorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
bool operator+(int*);
};
void target() { S{} + nullptr; }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator+"))));
}
TEST(CollectEvidenceFromImplementationTest, NonMemberOperatorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
bool operator+(const S&, int*);
void target() { S{} + nullptr; }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(1), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator+"))));
}
TEST(CollectEvidenceFromImplementationTest, VarArgs) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int*...);
void target() { callee(nullptr, nullptr); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, MemberOperatorCallVarArgs) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
bool operator()(int*...);
};
void target() { S{}(nullptr, nullptr); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator()"))));
}
TEST(CollectEvidenceFromImplementationTest, NotInferenceTarget) {
static constexpr llvm::StringRef Src = R"cc(
template <typename T>
T* target(T* p) {
*p;
return nullptr;
}
void instantiate() { target<int>(nullptr); }
)cc";
clang::TestAST AST(getInputsWithAnnotationDefinitions(Src));
auto TargetInstantiationNodes = match(
functionDecl(hasName("target"), isTemplateInstantiation()).bind("target"),
AST.context());
ASSERT_THAT(TargetInstantiationNodes, SizeIs(1));
auto* const InstantiationDecl = ast_matchers::selectFirst<FunctionDecl>(
"target", TargetInstantiationNodes);
ASSERT_NE(InstantiationDecl, nullptr);
std::vector<Evidence> Results;
auto Err = collectEvidenceFromImplementation(
*InstantiationDecl,
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); }));
if (Err) ADD_FAILURE() << toString(std::move(Err));
EXPECT_THAT(Results, IsEmpty());
}
TEST(CollectEvidenceFromDeclarationTest, VariableDeclIgnored) {
llvm::StringLiteral Src = "Nullable<int *> target;";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());
}
TEST(CollectEvidenceFromDeclarationTest, FunctionDeclReturnType) {
llvm::StringLiteral Src = "Nonnull<int *> target();";
EXPECT_THAT(
collectEvidenceFromTargetDecl(Src),
ElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::ANNOTATED_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, 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(CollectEvidenceFromDeclarationTest, FunctionDeclNonTopLevel) {
llvm::StringLiteral Src = "Nonnull<int*>** target(Nullable<int*>*);";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());
}
TEST(CollectEvidenceFromDeclarationTest, FunctionTemplateIgnored) {
// We used to inspect the type of `target` and crash.
llvm::StringLiteral Src = R"cc(
template <class A>
struct S {
template <class B>
static void target(const S<B>&) {}
};
)cc";
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 that are not templates.
EXPECT_THAT(Sites.Declarations, ElementsAre(declNamed("f<1>")));
// Instantiations are relevant inference targets.
EXPECT_THAT(Sites.Implementations,
ElementsAre(declNamed("f<0>"), declNamed("f<1>"),
declNamed("S::f<0>"), declNamed("T<0>::f")));
}
TEST(EvidenceEmitterTest, NotInferenceTarget) {
TestAST AST(R"cc(
template <int I>
int target() {
return I;
})cc");
const auto* TargetDecl =
dataflow::test::findValueDecl(AST.context(), "target");
ASSERT_NE(TargetDecl, nullptr);
EXPECT_DEATH(evidenceEmitter([](const Evidence& e) {})(
*TargetDecl, Slot{}, Evidence::ANNOTATED_UNKNOWN,
TargetDecl->getLocation()),
"not an inference target");
}
} // namespace
} // namespace clang::tidy::nullability