blob: 115c3bfc518a420be97974b5c9606bdf1135fc9f [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 "nullability/inference/slot_fingerprint.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/DenseMap.h"
#include "llvm/ADT/DenseSet.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::AllOf;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::Not;
using ::testing::ResultOf;
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;
template <typename T>
using Unknown [[clang::annotate("Nullability_Unspecified")]] = T;
)cc";
Inputs.ExtraArgs.push_back("-include");
Inputs.ExtraArgs.push_back("nullability.h");
return Inputs;
}
std::vector<Evidence> collectEvidenceFromTargetFunction(
llvm::StringRef Source, PreviousInferences PreviousInferences = {}) {
std::vector<Evidence> Results;
clang::TestAST AST(getInputsWithAnnotationDefinitions(Source));
USRCache usr_cache;
auto Err = collectEvidenceFromImplementation(
cast<FunctionDecl>(
*dataflow::test::findValueDecl(AST.context(), "target")),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
usr_cache),
usr_cache, PreviousInferences);
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));
USRCache usr_cache;
collectEvidenceFromTargetDeclaration(
*dataflow::test::findValueDecl(AST.context(), "target"),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
usr_cache));
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(CollectEvidenceFromImplementationTest, DerefArrow) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int x;
int y();
};
void target(S *a, S *b) {
a->x;
b->y();
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(1), 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, ConstructorCall) {
static constexpr llvm::StringRef Src = R"cc(
class S {
public:
S(Nonnull<int*> a);
};
void target(int* p) { S s(p); }
)cc";
EXPECT_THAT(
collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("S"))));
}
TEST(CollectEvidenceFromImplementationTest, PassedToNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nonnull<int*> i);
void target(int* p) { callee(p); }
)cc";
EXPECT_THAT(
collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromImplementationTest, AssignedToNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q, int* r) {
Nonnull<int*> a = p, b = q;
a = r;
}
)cc";
EXPECT_THAT(
collectEvidenceFromTargetFunction(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(1), Evidence::BOUND_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(2), Evidence::BOUND_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromImplementationTest, AssignedToNullableOrUnknown) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q, int* r) {
Nullable<int*> a = p;
int* b = q;
Unknown<int*> c = r;
q = r;
}
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
// A crash repro involving callable parameters.
TEST(CollectEvidenceFromImplementationTest, FunctionPointerParam) {
static constexpr llvm::StringRef Src = R"cc(
void target(void (*f)()) { f(); }
)cc";
EXPECT_THAT(collectEvidenceFromTargetFunction(Src), IsEmpty());
}
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);
USRCache usr_cache;
std::vector<Evidence> Results;
auto Err = collectEvidenceFromImplementation(
*InstantiationDecl,
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
usr_cache),
usr_cache);
if (Err) ADD_FAILURE() << toString(std::move(Err));
EXPECT_THAT(Results, IsEmpty());
}
TEST(CollectEvidenceFromImplementationTest, PropagatesPreviousInferences) {
static constexpr llvm::StringRef Src = R"cc(
void calledWithToBeNullable(int* x);
void calledWithToBeNonnull(int* a);
void target(int* p, int* q) {
target(nullptr, q);
calledWithToBeNullable(p);
*q;
calledWithToBeNonnull(q);
}
)cc";
std::string TargetUsr = "c:@F@target#*I#S0_#";
std::vector ExpectedBothRoundResults = {
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
AllOf(functionNamed("target"),
// Double-check that target's usr is as expected before we
// use it to create SlotFingerprints.
ResultOf([](Symbol S) { return S.usr(); }, TargetUsr))),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target")),
};
std::vector ExpectedSecondRoundResults = {
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("calledWithToBeNullable")),
evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("calledWithToBeNonnull"))};
// Only proceed if we have the correct USR for target and the first round
// results contain the evidence needed to produce our expected inferences and
// do not contain the evidence only found from propagating inferences from the
// first round.
auto FirstRoundResults = collectEvidenceFromTargetFunction(Src);
ASSERT_THAT(FirstRoundResults, IsSupersetOf(ExpectedBothRoundResults));
for (const auto& E : ExpectedSecondRoundResults) {
ASSERT_THAT(FirstRoundResults, Not(Contains(E)));
}
EXPECT_THAT(collectEvidenceFromTargetFunction(
Src, {/*Nullable=*/{fingerprint(TargetUsr, paramSlot(0))},
/*Nonnull=*/{fingerprint(TargetUsr, paramSlot(1))}}),
AllOf(IsSupersetOf(ExpectedBothRoundResults),
IsSupersetOf(ExpectedSecondRoundResults)));
}
TEST(CollectEvidenceFromImplementationTest,
AnalysisUsesPreviousInferencesForSlotsOutsideTargetImplementation) {
static constexpr llvm::StringRef Src = R"cc(
int* returnsToBeNonnull(int* a) {
return a;
}
int* target(int* q) {
*q;
return returnsToBeNonnull(q);
}
)cc";
std::string TargetUsr = "c:@F@target#*I#";
std::string ReturnsToBeNonnullUsr = "c:@F@returnsToBeNonnull#*I#";
const llvm::DenseMap<int, std::vector<testing::Matcher<const Evidence&>>>
ExpectedNewResultsPerRound = {
{0,
{evidence(
paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
AllOf(functionNamed("target"),
// Double-check that target's usr is as expected before
// we use it to create SlotFingerprints.
ResultOf([](Symbol S) { return S.usr(); }, TargetUsr)))}},
{1,
{evidence(
paramSlot(0), Evidence::NONNULL_ARGUMENT,
AllOf(functionNamed("returnsToBeNonnull"),
// Double-check that returnsToBeNonnull's usr is as
// expected before we use it to create SlotFingerprints.
ResultOf([](Symbol S) { return S.usr(); },
ReturnsToBeNonnullUsr)))}},
{2,
{
// No new evidence from target's implementation in this round,
// but in a full-TU analysis, this would be the round where we
// decide returnsToBeNonnull returns Nonnull, based on the
// now-Nonnull argument that is the only return value.
}},
{3,
{evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("target"))}}};
// Assert first round results because they don't rely on previous inference
// propagation at all and in this case are test setup and preconditions.
auto FirstRoundResults = collectEvidenceFromTargetFunction(Src);
ASSERT_THAT(FirstRoundResults,
IsSupersetOf(ExpectedNewResultsPerRound.at(0)));
for (const auto& E : ExpectedNewResultsPerRound.at(1)) {
ASSERT_THAT(FirstRoundResults, Not(Contains(E)));
}
auto SecondRoundResults = collectEvidenceFromTargetFunction(
Src, {.Nonnull = {fingerprint(TargetUsr, paramSlot(0))}});
EXPECT_THAT(SecondRoundResults,
AllOf(IsSupersetOf(ExpectedNewResultsPerRound.at(0)),
IsSupersetOf(ExpectedNewResultsPerRound.at(1))));
for (const auto& E : ExpectedNewResultsPerRound.at(2)) {
ASSERT_THAT(SecondRoundResults, Not(Contains(E)));
}
auto ThirdRoundResults = collectEvidenceFromTargetFunction(
Src, {.Nonnull = {fingerprint(TargetUsr, paramSlot(0)),
fingerprint(ReturnsToBeNonnullUsr, paramSlot(0))}});
EXPECT_THAT(ThirdRoundResults,
AllOf(IsSupersetOf(ExpectedNewResultsPerRound.at(0)),
IsSupersetOf(ExpectedNewResultsPerRound.at(1)),
IsSupersetOf(ExpectedNewResultsPerRound.at(2))));
for (const auto& E : ExpectedNewResultsPerRound.at(3)) {
ASSERT_THAT(ThirdRoundResults, Not(Contains(E)));
}
auto FourthRoundResults = collectEvidenceFromTargetFunction(
Src,
{.Nonnull = {
fingerprint(TargetUsr, paramSlot(0)),
fingerprint(ReturnsToBeNonnullUsr, paramSlot(0)),
// As noted in the Evidence matcher list above, we don't infer the
// return type of returnsToBeNonnull from only collecting evidence
// from target's implementation, but for the sake of this test, let's
// pretend we collected evidence from the entire TU.
fingerprint(ReturnsToBeNonnullUsr, SLOT_RETURN_TYPE)}});
EXPECT_THAT(FourthRoundResults,
AllOf(IsSupersetOf(ExpectedNewResultsPerRound.at(0)),
IsSupersetOf(ExpectedNewResultsPerRound.at(1)),
IsSupersetOf(ExpectedNewResultsPerRound.at(2)),
IsSupersetOf(ExpectedNewResultsPerRound.at(3))));
}
TEST(CollectEvidenceFromImplementationTest,
PreviousInferencesOfNonTargetParameterNullabilitiesPropagate) {
static constexpr llvm::StringRef Src = R"cc(
void takesToBeNonnull(int* a) {
// Not read when collecting evidence only from Target, but corresponding
// inference is explicitly input below.
*a;
}
void target(int* q) { takesToBeNonnull(q); }
)cc";
std::string TakesToBeNonnullUsr = "c:@F@takesToBeNonnull#*I#";
// Pretend that in a first round of inferring for all functions, we made this
// inference about takesToBeNonnull's first parameter.
// This test confirms that we use that information when collecting from
// target's implementation.
EXPECT_THAT(
collectEvidenceFromTargetFunction(
Src, {.Nonnull = {fingerprint(TakesToBeNonnullUsr, paramSlot(0))}}),
Contains(evidence(paramSlot(0), Evidence::BOUND_TO_NONNULL,
functionNamed("target"))));
}
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 {
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::S"),
declNamed("S::member"), declNamed("S::member")));
EXPECT_THAT(Sites.Implementations,
ElementsAre(declNamed("bar"), declNamed("baz"), declNamed("S::S"),
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 inferable.
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);
USRCache usr_cache;
EXPECT_DEATH(evidenceEmitter([](const Evidence& e) {}, usr_cache)(
*TargetDecl, Slot{}, Evidence::ANNOTATED_UNKNOWN,
TargetDecl->getLocation()),
"not an inference target");
}
} // namespace
} // namespace clang::tidy::nullability