blob: 0afc84ea2047a6e8a08bd3d053903b6154c6a149 [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 <cassert>
#include <memory>
#include <string>
#include <vector>
#include "nullability/inference/augmented_test_inputs.h"
#include "nullability/inference/inference.proto.h"
#include "nullability/inference/slot_fingerprint.h"
#include "nullability/pragma.h"
#include "nullability/type_nullability.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.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/raw_ostream.h"
#include "llvm/Testing/Support/Error.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::cxxConstructorDecl;
using ::clang::ast_matchers::functionDecl;
using ::clang::ast_matchers::hasName;
using ::clang::ast_matchers::isDefaultConstructor;
using ::clang::ast_matchers::isImplicit;
using ::clang::ast_matchers::isTemplateInstantiation;
using ::clang::ast_matchers::match;
using ::clang::ast_matchers::parameterCountIs;
using ::clang::ast_matchers::selectFirst;
using ::clang::ast_matchers::unless;
using ::clang::ast_matchers::varDecl;
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;
test::EnableSmartPointers Enable;
constexpr llvm::StringRef CheckMacroDefinitions = R"cc(
// Bodies must reference the first param so that args are in the AST, but
// otherwise don't matter.
#define CHECK(x) (x)
#define CHECK_NE(a, b) (a, b)
)cc";
MATCHER_P3(isEvidenceMatcher, SlotMatcher, KindMatcher, SymbolMatcher, "") {
return SlotMatcher.Matches(static_cast<Slot>(arg.slot())) &&
KindMatcher.Matches(arg.kind()) && SymbolMatcher.Matches(arg.symbol());
}
MATCHER_P(functionNamed, Name, "") {
return llvm::StringRef(arg.usr()).contains(
("@" + llvm::StringRef(Name) + "#").str());
}
/// Matches a non-static field with the given name.
/// The name should be of the form "MyStruct::field", but it should be qualified
/// only by the enclosing type, not any namespaces.
MATCHER_P(fieldNamed, TypeQualifiedFieldName, "") {
const auto [TypeName, FieldName] =
llvm::StringRef(TypeQualifiedFieldName).split("::");
return arg.usr().ends_with(("@S@" + TypeName + "@FI@" + FieldName).str()) ||
arg.usr().ends_with(("@U@" + TypeName + "@FI@" + FieldName).str());
}
/// Matches a static field with the given name.
/// The name should be of the form "MyStruct::field" (see also comment for
/// `fieldNamed()`).
MATCHER_P(staticFieldNamed, TypeQualifiedFieldName, "") {
const auto [TypeName, FieldName] =
llvm::StringRef(TypeQualifiedFieldName).split("::");
return arg.usr().ends_with(("@S@" + TypeName + "@" + FieldName).str());
}
MATCHER_P(globalVarNamed, Name, "") {
return arg.usr() == ("c:@" + llvm::StringRef(Name)).str();
}
testing::Matcher<const Evidence&> evidence(
testing::Matcher<Slot> S, testing::Matcher<Evidence::Kind> Kind,
testing::Matcher<const Symbol&> SymbolMatcher = functionNamed("target")) {
return isEvidenceMatcher(S, Kind, SymbolMatcher);
}
std::vector<Evidence> collectFromDefinition(
clang::TestAST& AST, const Decl& Definition,
const NullabilityPragmas& Pragmas,
PreviousInferences InputInferences = {}) {
std::vector<Evidence> Results;
USRCache UsrCache;
// Can't assert from within a non-void helper function, so only EXPECT.
EXPECT_THAT_ERROR(
collectEvidenceFromDefinition(
Definition,
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
UsrCache, AST.context()),
UsrCache, Pragmas, InputInferences),
llvm::Succeeded());
return Results;
}
std::vector<Evidence> collectFromDefinitionNamed(
llvm::StringRef TargetName, llvm::StringRef Source,
PreviousInferences InputInferences = {}) {
NullabilityPragmas Pragmas;
clang::TestAST AST(getAugmentedTestInputs(Source, Pragmas));
const Decl& Definition =
*dataflow::test::findValueDecl(AST.context(), TargetName);
return collectFromDefinition(AST, Definition, Pragmas, InputInferences);
}
/// Provides a default function-name-cased value for TargetName in
/// collectEvidenceFromDefinitionNamed, which puts TargetName first for
/// readability.
std::vector<Evidence> collectFromTargetFuncDefinition(
llvm::StringRef Source, PreviousInferences InputInferences = {}) {
return collectFromDefinitionNamed("target", Source, InputInferences);
}
template <typename MatcherT>
std::vector<Evidence> collectFromDefinitionMatching(
MatcherT Matcher, llvm::StringRef Source,
PreviousInferences InputInferences = {}) {
NullabilityPragmas Pragmas;
clang::TestAST AST(getAugmentedTestInputs(Source, Pragmas));
const Decl& Definition =
*selectFirst<Decl>("d", match(Matcher.bind("d"), AST.context()));
return collectFromDefinition(AST, Definition, Pragmas, InputInferences);
}
std::vector<Evidence> collectFromTargetDecl(llvm::StringRef Source) {
std::vector<Evidence> Results;
NullabilityPragmas Pragmas;
clang::TestAST AST(getAugmentedTestInputs(Source, Pragmas));
USRCache USRCache;
collectEvidenceFromTargetDeclaration(
*dataflow::test::findValueDecl(AST.context(), "target"),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
USRCache, AST.context()),
Pragmas);
return Results;
}
TEST(CollectEvidenceFromDefinitionTest, Location) {
llvm::StringRef Code = "void target(int *p) { *p; }";
// 12345678901234567890123456
// 0 1 2
auto Evidence = collectFromTargetFuncDefinition(Code);
ASSERT_THAT(Evidence, ElementsAre(evidence(paramSlot(0),
Evidence::UNCHECKED_DEREFERENCE)));
EXPECT_EQ("input.cc:1:23", Evidence.front().location());
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, Location) {
llvm::StringRef Code =
"#include <memory>\nvoid target(std::unique_ptr<int> p) { *p; }";
// 123456789012345678901234567890123456789012
// 0 1 2 3 4
auto Evidence = collectFromTargetFuncDefinition(Code);
ASSERT_THAT(Evidence, ElementsAre(evidence(paramSlot(0),
Evidence::UNCHECKED_DEREFERENCE)));
EXPECT_EQ("input.cc:2:39", Evidence.front().location());
}
TEST(CollectEvidenceFromDefinitionTest, NoParams) {
static constexpr llvm::StringRef Src = R"cc(
void target() {}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, OneParamUnused) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, OneParamUsedWithoutRestriction) {
static constexpr llvm::StringRef Src = R"cc(
void takesUnknown(int *unknown) {}
void target(int *p0) { takesUnknown(p0); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, Deref) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct S {
int x;
int y();
};
void target(std::unique_ptr<S> p) {
*p;
p->x;
p->y();
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE),
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, DerefOfNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void target(Nonnull<int *> p) {
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, DereferenceBeforeAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p) {
*p;
int i = 1;
p = &i;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, DereferenceAfterAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p) {
int i = 1;
p = &i;
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, DereferenceAfterAssignmentFromReturn) {
static constexpr llvm::StringRef Src = R"cc(
int& getIntRef();
int* getIntPtr();
void target(int* p) {
p = &getIntRef();
*p;
p = getIntPtr();
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(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(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, DerefBeforeGuardedDeref) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {
int a = *p0;
if (p0 != nullptr) {
int b = *p0;
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, DerefAndOrCheckOfCopiedPtr) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
int* a = p;
*a;
int* b = q;
if (q) {
*b;
}
if (b) {
*q;
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, FirstSufficientSlotOnly) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
// Marking either of p or q Nonnull is sufficient to avoid dereferencing
// without a check. We choose to record evidence only for the first
// sufficient slot which can be Nonnull without the dereference becoming
// dead code.
int* a;
if (p) {
a = p;
} else {
a = q;
}
*a;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest,
FirstSufficientSlotNotContradictingFlowConditions) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
// Marking p Nonnull would make the dereference dead, so we collect
// evidence for q being Nonnull instead, since it is also sufficient.
if (!p) {
*q;
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, EarlyReturn) {
static constexpr llvm::StringRef Src = R"cc(
void target(int *p0) {
if (!p0) {
return;
}
int a = *p0;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, 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(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE)));
}
TEST(CollectEvidenceFromDefinitionTest, PointerToMemberField) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
void target(int S::*p) {
S s;
s.*p;
(&s)->*p;
}
)cc";
// Pointers to members are not supported pointer types, so no evidence is
// collected. If they become a supported pointer type, this test should start
// failing.
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, PointerToMemberMethod) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
void target(void (S::*p)()) {
S s;
(s.*p)();
((&s)->*p)();
}
)cc";
// Pointers to members are not supported pointer types, so no evidence is
// collected. If they become a supported pointer type, this test should start
// failing.
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, PointerToMemberMethodArgs) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
void target(void (S::*p)(Nonnull<int*> i, int* j), int* q) {
S s;
(s.*p)(q, nullptr);
((&s)->*p)(q, nullptr);
}
)cc";
// Pointers to members are not supported pointer types, so no evidence is
// collected for `p` or `j`. If they become a supported pointer type, this
// test should start failing.
// TODO(b/309625642) We should still collect evidence for the use of `q` as an
// argument for param `i`.
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, CheckMacro) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
void target(int* p, int* q, int* r, int* s, int* t, int* u, int* v) {
// should collect evidence for params from these calls
CHECK(p);
CHECK(q != nullptr);
int* a = nullptr;
CHECK(r != a);
CHECK(a != s);
bool b = t != nullptr;
CHECK(b);
// should not crash when analyzing these calls
CHECK(u == v);
CHECK(u != v);
CHECK(1);
struct S {
operator bool() const { return true; }
};
CHECK(S());
CHECK(true);
CHECK(false); // must come last because it's detected as causing the rest
// of the function to be dead.
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ABORT_IF_NULL),
evidence(paramSlot(1), Evidence::ABORT_IF_NULL),
evidence(paramSlot(2), Evidence::ABORT_IF_NULL),
evidence(paramSlot(3), Evidence::ABORT_IF_NULL),
evidence(paramSlot(4), Evidence::ABORT_IF_NULL)));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, CheckMacro) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#include <memory>
void target(std::unique_ptr<int> p, std::unique_ptr<int> q,
std::unique_ptr<int> r) {
CHECK(p);
CHECK(!!q);
CHECK(r.get());
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ABORT_IF_NULL),
evidence(paramSlot(1), Evidence::ABORT_IF_NULL),
evidence(paramSlot(2), Evidence::ABORT_IF_NULL)));
}
TEST(CollectEvidenceFromDefinitionTest, CheckNEMacro) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
void target(int* p, int* q, int* r, int* s) {
// should collect evidence for params from these calls
CHECK_NE(p, nullptr);
CHECK_NE(nullptr, q);
int* a = nullptr;
CHECK_NE(a, r);
CHECK_NE(s, a);
// should not crash when analyzing these calls
CHECK_NE(a, 0);
int i = 1;
CHECK_NE(i, 0);
bool b = true;
CHECK_NE(true, false);
struct S {
bool operator==(const S&) const { return false; }
};
CHECK_NE(S(), S());
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ABORT_IF_NULL),
evidence(paramSlot(1), Evidence::ABORT_IF_NULL),
evidence(paramSlot(2), Evidence::ABORT_IF_NULL),
evidence(paramSlot(3), Evidence::ABORT_IF_NULL)));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, CheckNEMacro) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#include <memory>
void target(std::unique_ptr<int> p, std::unique_ptr<int> q,
std::unique_ptr<int> r, std::unique_ptr<int> s) {
CHECK_NE(p, nullptr);
CHECK_NE(nullptr, q);
if (!r) {
CHECK_NE(s, r);
}
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ABORT_IF_NULL),
evidence(paramSlot(1), Evidence::ABORT_IF_NULL),
evidence(paramSlot(3), Evidence::ABORT_IF_NULL)));
}
TEST(CollectEvidenceFromDefinitionTest, NullableArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(Nullable<int *> p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, NonnullArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(Nonnull<int *> p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, UnknownArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(int *p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, UnknownButProvablyNullArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(int *p) {
if (p == nullptr) {
callee(p);
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, CheckedArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int *q);
void target(int *p) {
if (p) callee(p);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, NullptrPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int* q);
void target() {
callee(nullptr);
int* p = nullptr;
callee(nullptr);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee")),
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, NonPtrArgPassed) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int q);
void target(int p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, ReferenceArgsPassed) {
static constexpr llvm::StringRef Src = R"cc(
void constCallee(int* const& a, int* const& b, int* const& c);
void mutableCallee(int*& a, int*& b, int*& c);
void target(Nullable<int*> p, Nonnull<int*> q, int* r) {
constCallee(p, q, r);
mutableCallee(p, q, r);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::NULLABLE_REFERENCE_ARGUMENT,
functionNamed("constCallee")),
evidence(paramSlot(1), Evidence::NONNULL_REFERENCE_ARGUMENT,
functionNamed("constCallee")),
evidence(paramSlot(2), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("constCallee")),
evidence(paramSlot(0), Evidence::NULLABLE_REFERENCE_ARGUMENT,
functionNamed("mutableCallee")),
evidence(paramSlot(1), Evidence::NONNULL_REFERENCE_ARGUMENT,
functionNamed("mutableCallee")),
evidence(paramSlot(2), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("mutableCallee"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, ArgsAndParams) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
void callee(std::unique_ptr<int> p, Nonnull<std::unique_ptr<int>> q,
Nullable<std::unique_ptr<int>>& r);
void target(Nullable<std::unique_ptr<int>> a, std::unique_ptr<int> b,
std::unique_ptr<int> c) {
callee(std::move(a), std::move(b), c);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee")),
evidence(paramSlot(1), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(2), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target")),
evidence(paramSlot(1), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee")),
evidence(paramSlot(2), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
DefaultArgumentsProduceNoEvidenceFromDefinition) {
static constexpr llvm::StringRef Src = R"cc(
int* getDefault();
void hasDefaultUnannotatedFunc(int* = getDefault());
int* q = nullptr;
void hasDefaultUnannotatedVariable(int* = getDefault());
int i = 1;
void hasDefaultExpressionOfVariable(int* = &i);
void target() {
hasDefaultUnannotatedFunc();
hasDefaultUnannotatedVariable();
hasDefaultExpressionOfVariable();
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, NullableReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target() { return nullptr; }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, NullableButCheckedReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(Nullable<int*> p) {
if (p) return p;
// no return in this path to avoid irrelevant evidence, and this still
// compiles, as the lack of return in a path is only a warning.
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, NonnullReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(Nonnull<int*> p) {
return p;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, UnknownReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p) { return p; }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, UnknownButProvablyNullReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p) {
if (p == nullptr) {
return p;
}
// no return in this path to avoid irrelevant evidence, and this still
// compiles, as the lack of return in a path is only a warning.
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, 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(
collectFromTargetFuncDefinition(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(CollectEvidenceFromDefinitionTest, ReferenceReturns) {
static constexpr llvm::StringRef Src = R"cc(
int*& target(Nonnull<int*>& p, Nullable<int*>& q, int*& r, bool a, bool b) {
if (a) return p;
if (b) return q;
return r;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_REFERENCE_RETURN),
evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_REFERENCE_RETURN),
evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_REFERENCE_RETURN)));
}
TEST(CollectEvidenceFromDefinitionTest, FromReturnAnnotation) {
static constexpr llvm::StringRef Src = R"cc(
Nonnull<int*> target(int* a) {
return a;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
FromPreviouslyInferredReturnAnnotation) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* a) { return a; }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(
Src, {.Nonnull = {fingerprint("c:@F@target#*I#", 0)}}),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
// We still collect evidence for the return type in case iteration
// turns up new evidence to contradict a previous inference. Only
// nullabilities written in source code are considered unchangeable.
evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_RETURN,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromAutoReturnAnnotationByPragma) {
static constexpr llvm::StringRef Src = R"cc(
#pragma nullability file_default nonnull
int* getNonnull();
// The pragma applies to the int* deduced for the `auto` return type,
// making the return type Nonnull<int*>.
auto target(NullabilityUnknown<int*> a, bool b) {
if (b) return a;
return getNonnull();
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
// This is a crash repro related to functions with AttributedTypeLocs.
TEST(CollectEvidenceFromDefinitionTest, FromReturnInAttributedFunction) {
static constexpr llvm::StringRef Src = R"cc(
struct AStruct {
const char* target() [[clang::lifetimebound]] { return nullptr; }
};
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, MultipleReturns) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
std::unique_ptr<int> target(Nonnull<std::unique_ptr<int>> p,
Nullable<std::unique_ptr<int>> q,
std::unique_ptr<int> r, bool a, bool b,
bool c) {
if (a) return nullptr;
if (b) return p;
if (c) return q;
return r;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN),
evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN),
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN),
evidence(SLOT_RETURN_TYPE, Evidence::UNKNOWN_RETURN)));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, FromReturnAnnotation) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
Nonnull<std::unique_ptr<int>> target(std::unique_ptr<int> a) {
return a;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL)));
}
TEST(CollectEvidenceFromDefinitionTest, FunctionCallDereferenced) {
static constexpr llvm::StringRef Src = R"cc(
int* makePtr();
void target() { *makePtr(); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("makePtr"))));
}
TEST(CollectEvidenceFromDefinitionTest,
FunctionCallResultDereferencedAfterAssignedLocally) {
static constexpr llvm::StringRef Src = R"cc(
int* makePtr();
void target() {
auto p = makePtr();
*p;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("makePtr"))));
}
TEST(CollectEvidenceFromDefinitionTest,
FunctionCallResultDereferencedAfterAssignedLocallyAndChecked) {
static constexpr llvm::StringRef Src = R"cc(
int* makePtr();
void target() {
auto p = makePtr();
if (p) *p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest,
FunctionCallResultDereferencedAfterUnrelatedConditionChecked) {
static constexpr llvm::StringRef Src = R"cc(
int* makePtr();
void target(bool cond) {
auto p = makePtr();
if (cond) *p;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("makePtr"))));
}
TEST(CollectEvidenceFromDefinitionTest, FunctionCallDereferencedWithArrow) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
void member();
};
S* makePtr();
void target() { makePtr()->member(); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("makePtr"))));
}
TEST(CollectEvidenceFromDefinitionTest,
AlreadyNonnullFunctionCallDereferenced) {
static constexpr llvm::StringRef Src = R"cc(
Nonnull<int*> makeNonnullPtr();
void target() { *makeNonnullPtr(); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, FunctionPointerCall) {
static constexpr llvm::StringRef Src = R"cc(
void target(void (*f)()) { f(); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
// This is a crash repro; see b/352043668.
TEST(CollectEvidenceFromDefinitionTest, FunctionPointerCallThroughBindingDecl) {
static constexpr llvm::StringRef Src = R"cc(
template <typename A, typename B>
struct Pair {
Pair();
A a;
B b;
};
void target(int *i) {
Pair<void (*)(Nonnull<int *>), bool> p;
auto [fp, b] = p;
fp(i);
}
)cc";
// Ideally, we would see the Nonnull from `p`'s template parameter and collect
// ASSIGNED_TO_NONNULL evidence for `i`, but the sugar doesn't carry through
// the BindingDecl's `auto` type.
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, ConstAccessorDereferencedAfterCheck) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int* accessor() const { return i; }
int* i = nullptr;
};
void target() {
S s;
if (s.accessor() != nullptr) {
*s.accessor();
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
// Special modeling of accessors is not implemented for accessors returning
// references.
TEST(CollectEvidenceFromDefinitionTest,
ReferenceConstAccessorDereferencedAfterCheck) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int* const& accessor() const { return i; }
int* i = nullptr;
};
void target() {
S s;
if (s.accessor() != nullptr) {
*s.accessor();
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("accessor"))));
}
TEST(CollectEvidenceFromDefinitionTest,
ConstAccessorOnTwoDifferentObjectsDereferencedAfterCheck) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int* const& accessor() const { return i; }
int* i = nullptr;
};
S makeS();
void target() {
if (makeS().accessor() != nullptr) {
*makeS().accessor();
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("accessor"))));
}
TEST(CollectEvidenceFromDefinitionTest, MemberCallOperatorReturnDereferenced) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int* operator()();
};
void target() {
S s;
*s();
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
Contains(evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("operator()"))));
}
TEST(CollectEvidenceFromDefinitionTest, MemberOperatorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
bool operator+(int*);
};
void target() { S{} + nullptr; }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator+"))));
}
TEST(CollectEvidenceFromDefinitionTest, NonMemberOperatorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
bool operator+(const S&, int*);
void target() { S{} + nullptr; }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(1), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator+"))));
}
TEST(CollectEvidenceFromDefinitionTest, VarArgs) {
static constexpr llvm::StringRef Src = R"cc(
void callee(int*...);
void target() { callee(nullptr, nullptr); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, MemberOperatorCallVarArgs) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
bool operator()(int*...);
};
void target() { S{}(nullptr, nullptr); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("operator()"))));
}
TEST(CollectEvidenceFromDefinitionTest, ConstructorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
S(Nonnull<int*> a);
};
void target(int* p) { S s(p); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("S"))));
}
TEST(CollectEvidenceFromDefinitionTest, NonTargetConstructorCall) {
static constexpr llvm::StringRef Src = R"cc(
template <typename T>
struct S {
// Not a target due to templating, but the annotation here can still
// provide evidence for `p` from the call in `target`'s body.
S(Nonnull<T*> a);
};
void target(int* p) { S s(p); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, ConstructorWithBaseInitializer) {
static constexpr llvm::StringRef Src = R"cc(
struct TakeNonnull {
explicit TakeNonnull(Nonnull<int *>);
};
struct target : TakeNonnull {
target(int *i) : TakeNonnull(i) {}
};
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, ConstructorWithDelegatingConstructor) {
static constexpr llvm::StringRef Src = R"cc(
struct target {
target(int* i);
target() : target(nullptr){};
};
)cc";
EXPECT_THAT(collectFromDefinitionMatching(
functionDecl(hasName("target"), parameterCountIs(0)), Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, VariadicConstructorCall) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
S(Nonnull<int*> i, ...);
};
void target(int* p, int* q) { S s(p, q); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("S"))));
}
TEST(CollectEvidenceFromDefinitionTest, FieldInitializerFromAssignmentToType) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target(int *Input) : I(Input) {}
Nonnull<int *> I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, DefaultFieldInitializerNullptr) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target() {};
int* I = nullptr;
};
)cc";
EXPECT_THAT(collectFromDefinitionMatching(
cxxConstructorDecl(isDefaultConstructor()), Src),
UnorderedElementsAre(evidence(
Slot(0), Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER,
fieldNamed("Target::I"))));
}
TEST(CollectEvidenceFromDefinitionTest, DefaultFieldInitializerNullable) {
static constexpr llvm::StringRef Src = R"cc(
Nullable<int*> G;
struct Target {
Target() {};
int* I = G;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(cxxConstructorDecl(isDefaultConstructor()),
Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Target::I"))));
}
TEST(CollectEvidenceFromDefinitionTest,
IndirectFieldInitializerFromAssignmentToType) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target(int *Input) : I(Input) {}
struct {
Nonnull<int *> I;
};
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, IndirectFieldDefaultFieldInitializer) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target() {}
struct {
int* I = nullptr;
};
};
// Use the implicitly-declared default constructor, so that it will be
// generated.
Target T;
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(isDefaultConstructor(), hasName("Target")), Src),
UnorderedElementsAre(
evidence(Slot(0), Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER,
fieldNamed("Target@Sa::I"))));
}
TEST(CollectEvidenceFromDefinitionTest, FieldInitializedWithNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target(Nullable<int *> Input) : I(Input) {}
int *I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Target::I"))));
}
TEST(EvidenceEmitterTest, UnionFieldInitializedWithNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Target {
Target() : Field{nullptr} {};
union UnionType {
int* I;
bool* B;
} Field;
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("UnionType::I"))));
}
TEST(CollectEvidenceFromDefinitionTest, FieldInitializerCallsFunction) {
static constexpr llvm::StringRef Src = R"cc(
int* getIntPtr(int*);
struct Target {
Target() : I(getIntPtr(nullptr)) {}
Nonnull<int*> I;
};
)cc";
EXPECT_THAT(collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_NONNULL,
functionNamed("getIntPtr")),
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("getIntPtr"))));
}
TEST(CollectEvidenceFromDefinitionTest, DefaultFieldInitializerCallsFunction) {
static constexpr llvm::StringRef Src = R"cc(
int* getIntPtr(int*);
struct Target {
Target() = default;
Nonnull<int*> I = getIntPtr(nullptr);
};
// Use the explicitly-declared but still implicitly-defined default
// constructor, so that it will be generated.
Target T;
)cc";
EXPECT_THAT(collectFromDefinitionMatching(
cxxConstructorDecl(isDefaultConstructor()), Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_NONNULL,
functionNamed("getIntPtr")),
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("getIntPtr"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
FieldInitializerFromAssignmentToType) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
struct Target {
Target(std::unique_ptr<int> Input) : I(std::move(Input)) {}
Nonnull<std::unique_ptr<int>> I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(unless(isImplicit()), hasName("Target")), Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("Target"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
FieldInitializedFromNullable) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
struct Target {
Target(Nullable<std::unique_ptr<int>> Input) : I(std::move(Input)) {}
std::unique_ptr<int> I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(unless(isImplicit()), hasName("Target")), Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Target::I"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
DefaultFieldInitializerNullptr) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct Target {
std::unique_ptr<int> I = nullptr;
};
// Use the implicitly-declared default constructor, so that it will be
// generated.
Target T;
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(isDefaultConstructor(), hasName("Target")), Src),
UnorderedElementsAre(
evidence(Slot(0), Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER,
fieldNamed("Target::I"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
DefaultFieldInitializerAbsentOnlyImplicitConstructor) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct Target {
std::unique_ptr<int> I;
};
// Use the implicitly-declared default constructor, so that it will be
// generated.
Target T;
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(isDefaultConstructor(), hasName("Target")), Src),
// By the end of the constructor body, the field is still only
// default-initialized, which for smart pointers means it is null.
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Target::I"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
DefaultFieldInitializerAbsentInitializedInConstructor) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct Target {
Target(int Input) { I = std::make_unique<int>(Input); }
std::unique_ptr<int> I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(unless(isImplicit()), hasName("Target")), Src),
// No evidence collected from constructor body, which assigns a Nonnull
// value, and no evidence collected from *implicit* member initializer
// which default constructs to null.
IsEmpty());
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
DefaultFieldInitializerAbsentConditionalAssignmentInConstructor) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct Target {
Target(int Input) {
if (Input != 0) {
I = std::make_unique<int>(Input);
}
}
std::unique_ptr<int> I;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(unless(isImplicit()), hasName("Target")), Src),
// By the end of the constructor body, the field is still potentially
// default-initialized, which for smart pointers means it may be null.
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Target::I"))));
}
// This is a crash repro.
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
ConstructorExitingWithUnmodeledField) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
struct Target {
// other.Field is not modeled and has no null state attached. Its value is
// coped into this.Field, leaving it without null state at the end of the
// constructor.
Target(Target& other) { *this = other; }
Target& operator=(const Target& other);
std::unique_ptr<int> Field;
};
)cc";
EXPECT_THAT(
collectFromDefinitionMatching(
cxxConstructorDecl(unless(isImplicit()), hasName("Target")), Src),
IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nonnull<int*> i);
void target(int* p) { callee(p); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNonnullRef) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nonnull<int*>& i);
void target(int* p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNonnullInMemberFunction) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
void callee(Nonnull<int*> i);
};
void target(int* p) {
S s;
s.callee(p);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNonnullInFunctionPointerParam) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, void (*callee)(Nonnull<int*> i)) {
callee(p);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
PassedToNonnullInFunctionPointerParam) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
void target(std::unique_ptr<int*> p,
void (*callee)(Nonnull<std::unique_ptr<int*>> i)) {
callee(std::move(p));
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNonnullInFunctionPointerField) {
static constexpr llvm::StringRef Src = R"cc(
struct MyStruct {
void (*callee)(Nonnull<int*>);
};
void target(int* p) { MyStruct().callee(p); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
fieldNamed("MyStruct::callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
PassedToNonnullInFunctionPointerFromAddressOfFunctionDecl) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nonnull<int*> i);
void target(int* p) { (&callee)(p); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
PassedToNonnullInFunctionReferenceParam) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, void (&callee)(Nonnull<int*> i)) {
callee(p);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
PassedToNonnullInFunctionPointerReferenceParam) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, void (*&callee)(Nonnull<int*> i)) {
callee(p);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(1), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, FunctionCallPassedToNonnull) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nonnull<int*> i);
int* makeIntPtr();
void target() { callee(makeIntPtr()); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_NONNULL,
functionNamed("makeIntPtr")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
FunctionCallPassedToNonnullFunctionPointer) {
static constexpr llvm::StringRef Src = R"cc(
int* makeIntPtr();
void target(void (*callee)(Nonnull<int*> i)) { callee(makeIntPtr()); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_NONNULL,
functionNamed("makeIntPtr")),
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
FunctionCallPassedToNonnullFunctionPointerTargetNotAnInferenceTarget) {
static constexpr llvm::StringRef Src = R"cc(
int* makeIntPtr();
template <typename T>
void target(void (*callee)(Nonnull<T*> i), int* a) {
callee(makeIntPtr());
*a;
}
void instantiate() {
target<int>([](Nonnull<int*> i) {}, nullptr);
}
)cc";
// Doesn't collect any evidence for target from target's body, only collects
// some for makeIntPtr.
EXPECT_THAT(
collectFromDefinitionMatching(
functionDecl(hasName("target"), isTemplateInstantiation()), Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::ASSIGNED_TO_NONNULL,
functionNamed("makeIntPtr"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNullable) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nullable<int*> i);
void target(int* p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNullableRef) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nullable<int*>& i);
void target(int* p) { callee(p); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
PassedToNullableRefFromStoredFunctionCall) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nullable<int*>& i);
int* producer();
void target() {
auto p = producer();
callee(p);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("producer")),
evidence(paramSlot(0), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest, PassedToNullableRefFromFunctionCall) {
static constexpr llvm::StringRef Src = R"cc(
void callee(Nullable<int*>& i);
int*& producer();
void target() { callee(producer()); }
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("producer")),
evidence(paramSlot(0), Evidence::UNKNOWN_REFERENCE_ARGUMENT,
functionNamed("callee"))));
}
TEST(CollectEvidenceFromDefinitionTest,
InitializationOfAndAssignmentToNonnull) {
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(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(1), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(paramSlot(2), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
InitializationOfAndAssignmentToNonnull) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
struct S {
Nonnull<std::unique_ptr<int>> a;
Nonnull<std::unique_ptr<int>>& getRef();
};
void target(std::unique_ptr<int> p, Nonnull<std::unique_ptr<int>> q,
std::unique_ptr<int> r, std::unique_ptr<int> s,
std::unique_ptr<int> t) {
q = std::move(p);
S AnS;
AnS.a = std::move(r);
AnS.getRef() = std::move(s);
Nonnull<std::unique_ptr<int>> nonnull = std::move(t);
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL),
evidence(paramSlot(2), Evidence::ASSIGNED_TO_NONNULL),
evidence(paramSlot(3), Evidence::ASSIGNED_TO_NONNULL),
evidence(paramSlot(4), Evidence::ASSIGNED_TO_NONNULL)));
}
TEST(CollectEvidenceFromDefinitionTest, InitializationOfNonnullRefFromRef) {
static constexpr llvm::StringRef Src = R"cc(
void target(int*& p) {
Nonnull<int*>& a = p;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
NonnullNonTargetInitializedFromFunctionCall) {
static constexpr llvm::StringRef Src = R"cc(
int* makeIntPtr();
template <typename T>
void target() {
Nonnull<T*> p = makeIntPtr();
}
void instantiate() { target<int>(); }
)cc";
// Doesn't collect any evidence for target from target's body, only collects
// some for makeIntPtr.
EXPECT_THAT(
collectFromDefinitionMatching(
functionDecl(hasName("target"), isTemplateInstantiation()), Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::ASSIGNED_TO_NONNULL,
functionNamed("makeIntPtr"))));
}
TEST(CollectEvidenceFromDefinitionTest,
InitializationOfAndAssignmentToNullableOrUnknown) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q, int* r) {
Nullable<int*> a = p;
int* b = q;
NullabilityUnknown<int*> c = r;
q = r;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, InitializationOfNullableRef) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p) {
Nullable<int*>& a = p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest,
InitializationOfNullableRef) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void target(std::unique_ptr<int> p) {
Nullable<std::unique_ptr<int>>& a = p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, InitializationOfNullableRefFromRef) {
static constexpr llvm::StringRef Src = R"cc(
void target(int*& p) {
Nullable<int*>& a = p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target"))));
}
// Ternary expressions are not currently modeled correctly by the analysis, but
// are necessary to test the case of multiple connected decls.
//
// DISABLED until ternary expressions are handle.
TEST(CollectEvidenceFromDefinitionTest,
DISABLED_InitializationOfNullableRefAllConnectedDecls) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q, bool b) {
Nullable<int*>& x = b ? p : q;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target")),
evidence(paramSlot(1), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromNullptr) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
q = nullptr;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(1),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromNullptrIndirect) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p) {
int* a = nullptr;
p = a;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromZero) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
q = 0;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(1),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromNullable) {
static constexpr llvm::StringRef Src = R"cc(
Nullable<int*> getNullable();
void target(int* p) { p = getNullable(); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromLocalNullable) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p) {
Nullable<int*> a;
p = a;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromNullableMemberCallExpr) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
int*& getPtrRef();
};
void target(S AnS) { AnS.getPtrRef() = nullptr; }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("getPtrRef"))));
}
TEST(CollectEvidenceFromDefinitionTest, AssignedFromNullptrMultipleOperators) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p) {
*&p = nullptr;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("target"))));
}
// This is a regression test for a bug where we collected ASSIGNED_FROM_NULLABLE
// evidence for the return type of `foo`, because the LHS type of the assignment
// was already nullable, and so any formula does imply that the LHS type of the
// assignment is nullable.
TEST(CollectEvidenceFromDefinitionTest,
AnnotatedLocalAssignedFromNullableAfterFunctionReturn) {
static constexpr llvm::StringRef Src = R"cc(
int* foo();
void target() {
Nullable<int*> p = foo();
p = nullptr;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest,
IrrelevantAssignmentsAndInitializations) {
static constexpr llvm::StringRef Src = R"cc(
struct S {
S(int* i);
};
void target(int* p) {
int* a = p; // No useful information.
// We don't collect if types on either side are not a supported pointer
// type.
int* b = 0;
int c = 4;
bool d = false;
S e = a;
// We don't collect from compound assignments.
b += 8;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
// From the constructor call constructing an S; no evidence from
// assignments or initializations.
UnorderedElementsAre(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("S"))));
}
TEST(CollectEvidenceFromDefinitionTest, Arithmetic) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* a, int* b, int* c, int* d, int* e, int* f, int* g,
int* h) {
a += 1;
b -= 2;
c + 3;
d - 4;
e++;
++f;
g--;
--h;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(1), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(2), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(3), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(4), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(5), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(6), Evidence::ARITHMETIC, functionNamed("target")),
evidence(paramSlot(7), Evidence::ARITHMETIC,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, Fields) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#include <memory>
void takesNonnull(Nonnull<int*>);
void takesMutableNullable(Nullable<int*>&);
struct S {
int* Deref;
int* AssignedToNonnull;
int* AssignedToMutableNullable;
int* AbortIfNull;
int* AbortIfNullBool;
int* AbortIfNullNE;
int* AssignedFromNullable;
int* Arithmetic;
std::unique_ptr<int> SmartDeref;
};
void target(S AnS) {
*AnS.Deref;
takesNonnull(AnS.AssignedToNonnull);
takesMutableNullable(AnS.AssignedToMutableNullable);
CHECK(AnS.AbortIfNull);
CHECK(AnS.AbortIfNullBool != nullptr);
CHECK_NE(AnS.AbortIfNullNE, nullptr);
AnS.AssignedFromNullable = nullptr;
AnS.Arithmetic += 4;
*AnS.SmartDeref;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
IsSupersetOf(
{evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
fieldNamed("S::Deref")),
evidence(Slot(0), Evidence::ASSIGNED_TO_NONNULL,
fieldNamed("S::AssignedToNonnull")),
evidence(Slot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
fieldNamed("S::AssignedToMutableNullable")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
fieldNamed("S::AbortIfNull")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
fieldNamed("S::AbortIfNullBool")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
fieldNamed("S::AbortIfNullNE")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("S::AssignedFromNullable")),
evidence(Slot(0), Evidence::ARITHMETIC, fieldNamed("S::Arithmetic")),
evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
fieldNamed("S::SmartDeref"))}));
}
TEST(CollectEvidenceFromDefinitionTest, StaticMemberVariables) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#include <memory>
void takesNonnull(Nonnull<int*>);
void takesMutableNullable(Nullable<int*>&);
struct MyStruct {
static int* Deref;
static int* AssignedToNonnull;
static int* AssignedToMutableNullable;
static int* AbortIfNull;
static int* AbortIfNullBool;
static int* AbortIfNullNE;
static int* AssignedFromNullable;
static int* Arithmetic;
static std::unique_ptr<int> SmartDeref;
};
void target() {
*MyStruct::Deref;
takesNonnull(MyStruct::AssignedToNonnull);
takesMutableNullable(MyStruct::AssignedToMutableNullable);
CHECK(MyStruct::AbortIfNull);
CHECK(MyStruct::AbortIfNullBool != nullptr);
CHECK_NE(MyStruct::AbortIfNullNE, nullptr);
MyStruct::AssignedFromNullable = nullptr;
MyStruct::Arithmetic += 4;
*MyStruct::SmartDeref;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
IsSupersetOf(
{evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
staticFieldNamed("MyStruct::Deref")),
evidence(Slot(0), Evidence::ASSIGNED_TO_NONNULL,
staticFieldNamed("MyStruct::AssignedToNonnull")),
evidence(Slot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
staticFieldNamed("MyStruct::AssignedToMutableNullable")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
staticFieldNamed("MyStruct::AbortIfNull")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
staticFieldNamed("MyStruct::AbortIfNullBool")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
staticFieldNamed("MyStruct::AbortIfNullNE")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
staticFieldNamed("MyStruct::AssignedFromNullable")),
evidence(Slot(0), Evidence::ARITHMETIC,
staticFieldNamed("MyStruct::Arithmetic")),
evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
staticFieldNamed("MyStruct::SmartDeref"))}));
}
TEST(CollectEvidenceFromDefinitionTest, Globals) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#include <memory>
void takesNonnull(Nonnull<int*>);
void takesMutableNullable(Nullable<int*>&);
int* Deref;
int* AssignedToNonnull;
int* AssignedToMutableNullable;
int* AbortIfNull;
int* AbortIfNullBool;
int* AbortIfNullNE;
int* AssignedFromNullable;
int* Arithmetic;
std::unique_ptr<int> SmartDeref;
void target() {
*Deref;
takesNonnull(AssignedToNonnull);
takesMutableNullable(AssignedToMutableNullable);
CHECK(AbortIfNull);
CHECK(AbortIfNullBool != nullptr);
CHECK_NE(AbortIfNullNE, nullptr);
AssignedFromNullable = nullptr;
Arithmetic += 4;
*SmartDeref;
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src.str()),
IsSupersetOf({evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
globalVarNamed("Deref")),
evidence(Slot(0), Evidence::ASSIGNED_TO_NONNULL,
globalVarNamed("AssignedToNonnull")),
evidence(Slot(0), Evidence::ASSIGNED_TO_MUTABLE_NULLABLE,
globalVarNamed("AssignedToMutableNullable")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
globalVarNamed("AbortIfNull")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
globalVarNamed("AbortIfNullBool")),
evidence(Slot(0), Evidence::ABORT_IF_NULL,
globalVarNamed("AbortIfNullNE")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
globalVarNamed("AssignedFromNullable")),
evidence(Slot(0), Evidence::ARITHMETIC,
globalVarNamed("Arithmetic")),
evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
globalVarNamed("SmartDeref"))}));
}
TEST(CollectEvidenceFromDefinitionTest, GlobalInit) {
static constexpr llvm::StringRef Src = R"cc(
int* getPtr();
Nullable<int*> getNullableFromNonnull(Nonnull<int*>);
int* Target = static_cast<int*>(getNullableFromNonnull(getPtr()));
)cc";
EXPECT_THAT(collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("getNullableFromNonnull")),
evidence(SLOT_RETURN_TYPE, Evidence::ASSIGNED_TO_NONNULL,
functionNamed("getPtr")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
globalVarNamed("Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, GlobalInitFromGlobalAnnotation) {
static constexpr llvm::StringRef Src = R"cc(
int* foo();
Nonnull<int*> Target = foo();
)cc";
EXPECT_THAT(collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::ASSIGNED_TO_NONNULL,
functionNamed("foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, GlobalSmartImplicitInit) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
// This has an implicit init because of default construction.
std::unique_ptr<int> Target;
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
globalVarNamed("Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, GlobalSmartExplicitInit) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
std::unique_ptr<int> Target = nullptr;
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
globalVarNamed("Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, StaticInitInClass) {
static constexpr llvm::StringRef Src = R"cc(
struct MyStruct {
inline static int* Target = nullptr;
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Target", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
staticFieldNamed("MyStruct::Target"))));
}
AST_MATCHER(VarDecl, hasInit) { return Node.hasInit(); }
TEST(CollectEvidenceFromDefinitionTest, StaticInitOutOfClass) {
static constexpr llvm::StringRef Src = R"cc(
struct MyStruct {
static int* Target;
};
int* MyStruct::Target = nullptr;
)cc";
EXPECT_THAT(collectFromDefinitionMatching(varDecl(hasInit()), Src),
UnorderedElementsAre(
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
staticFieldNamed("MyStruct::Target"))));
}
TEST(CollectEvidenceFromDefinitionTest, NoEvidenceForLocals) {
static constexpr llvm::StringRef Src = R"cc(
void target() {
int* p = nullptr;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, FunctionCallInLoop) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p) {
for (int i = 0; i < 3; ++i) {
target(nullptr);
}
for (int i = 0; i < 3; ++i) {
target(&i);
}
for (int i = 0; i < 3; ++i) {
target(p);
}
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target")),
evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("target")),
evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, OutputParameterPointerToPointer) {
static constexpr llvm::StringRef Src = R"cc(
void maybeModifyPtr(int** a);
void target(int* p) {
maybeModifyPtr(&p);
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, OutputParameterReferenceToPointer) {
static constexpr llvm::StringRef Src = R"cc(
void maybeModifyPtr(int*& a);
void target(int* p) {
maybeModifyPtr(p);
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest,
OutputParameterReferenceToConstPointer) {
static constexpr llvm::StringRef Src = R"cc(
void dontModifyPtr(int* const& a);
void target(int* p) {
dontModifyPtr(p);
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
OutputParameterReferenceToPointerToPointer) {
static constexpr llvm::StringRef Src = R"cc(
void maybeModifyPtr(int**& a);
void target(int** p) {
maybeModifyPtr(p);
*p;
**p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, OutputParameterPointerToConstPointer) {
static constexpr llvm::StringRef Src = R"cc(
void dontModifyPtr(int* const* a);
void target(int* p) {
dontModifyPtr(&p);
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
OutputParameterConstPointerToPointerToConst) {
static constexpr llvm::StringRef Src = R"cc(
// Outer pointer and int are const, but inner pointer can still be modified.
void maybeModifyPtr(const int** const a);
void target(const int* p) {
maybeModifyPtr(&p);
*p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, PassAsOutputParameterOrDereference) {
static constexpr llvm::StringRef Src = R"cc(
void maybeModifyPtr(int** a);
void target(int* p, bool b) {
if (b) {
maybeModifyPtr(&p);
} else {
*p;
}
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest,
ConditionallyPassAsOutputParameterAlwaysDereference) {
static constexpr llvm::StringRef Src = R"cc(
void maybeModifyPtr(int** a);
void target(int* p, bool b) {
if (b) maybeModifyPtr(&p);
*p; // Because we model p as Unknown post-output-parameter-use, adding an
// annotation would not be considered sufficient to make this
// dereference safe, so we do not collect evidence for p.
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Not(Contains(evidence(_, _, functionNamed("target")))));
}
TEST(CollectEvidenceFromDefinitionTest, FromGlobalLabmdaBodyForGlobal) {
static constexpr llvm::StringRef Src = R"cc(
int* p;
auto Lambda = []() { *p; };
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("operator()", Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::UNCHECKED_DEREFERENCE,
globalVarNamed("p"))));
}
// TODO(b/315967534) Collect for captured function parameters, specifically from
// the unchecked dereference of `foo`'s parameter.
TEST(CollectEvidenceFromDefinitionTest, FromLocalLambdaForCapturedParam) {
static constexpr llvm::StringRef Src = R"cc(
void foo(int* p) {
auto Lambda = [&p]() { *p; };
}
)cc";
EXPECT_THAT(collectFromDefinitionNamed("operator()", Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest, FromLocalLambdaForCalledFunction) {
static constexpr llvm::StringRef Src = R"cc(
int* bar();
void foo() {
auto Lambda = []() { *bar(); };
}
)cc";
EXPECT_THAT(collectFromDefinitionNamed("operator()", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("bar"))));
}
TEST(CollectEvidenceFromDefinitionTest, ForLambdaParamOrReturn) {
static constexpr llvm::StringRef Src = R"cc(
auto Lambda = [](int* p) -> int* {
*p;
return nullptr;
};
)cc";
EXPECT_THAT(collectFromDefinitionNamed("operator()", Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("operator()")),
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("operator()"))));
}
TEST(CollectEvidenceFromDefinitionTest, AggregateInitialization) {
static constexpr llvm::StringRef Header = R"cc(
struct Base {
bool* BaseB;
Nonnull<char*> BaseC;
};
struct MyStruct : public Base {
int* I;
bool* B;
};
)cc";
const llvm::Twine BracesAggInit = Header + R"cc(
void target(Nullable<bool*> Bool, char* Char) {
MyStruct{Bool, Char, nullptr, Bool};
}
)cc";
// New aggregate initialization syntax in C++20
const llvm::Twine ParensAggInit = Header + R"cc(
void target(Nullable<bool*> Bool, char* Char) {
MyStruct(Base(Bool, Char), nullptr, Bool);
}
)cc";
auto ExpectedEvidenceMatcher =
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("Base::BaseB")),
evidence(paramSlot(1), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("MyStruct::I")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("MyStruct::B")));
EXPECT_THAT(collectFromTargetFuncDefinition(BracesAggInit.str()),
ExpectedEvidenceMatcher);
EXPECT_THAT(collectFromTargetFuncDefinition(ParensAggInit.str()),
ExpectedEvidenceMatcher);
}
TEST(SmartPointerCollectEvidenceFromDefinitionTest, AggregateInitialization) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
#include <utility>
struct MyStruct {
std::unique_ptr<int> p;
Nonnull<std::unique_ptr<int>> q;
std::unique_ptr<int> r;
};
void target(Nullable<std::unique_ptr<int>> a, std::unique_ptr<int> b) {
MyStruct{std::move(a), std::move(b), nullptr};
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("MyStruct::p")),
evidence(paramSlot(1), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target")),
evidence(Slot(0), Evidence::ASSIGNED_FROM_NULLABLE,
fieldNamed("MyStruct::r"))));
}
// This is a crash repro related to aggregate initialization.
TEST(CollectEvidenceFromDefinitionTest, NonRecordInitListExpr) {
static constexpr llvm::StringRef Src = R"cc(
void target() { int a[3] = {}; }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
TEST(CollectEvidenceFromDefinitionTest,
SmartPointerAnalysisProvidesEvidenceForRawPointer) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void foo(int*);
void target(Nullable<std::unique_ptr<int>> p) { foo(p.get()); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("foo"))));
}
// This is a crash repro related to non-aggregate initialization using an
// InitListExpr.
TEST(CollectEvidenceFromDefinitionTest, TransparentInitListExpr) {
static constexpr llvm::StringRef Src = R"cc(
struct S {};
void foo(S p) {}
S get();
void target() { foo({get()}); }
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src), IsEmpty());
}
// Evidence for return type nonnull-ness should flow only from derived to base,
// so we collect evidence for the base but not the derived.
TEST(CollectEvidenceFromDefinitionTest, FromVirtualDerivedForReturnNonnull) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual int* foo();
};
struct Derived : public Base {
int* foo() override {
static int i;
return &i;
}
};
void target() {
Derived D;
*D.foo();
}
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Derived::foo", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("Derived@F@foo"))));
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE,
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Derived@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualDerivedForReturnNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual int* foo();
};
struct Derived : public Base {
int* foo() override { return nullptr; }
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Derived::foo", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("Derived@F@foo")),
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("Base@F@foo"))));
// We don't currently have any evidence kinds that can force a non-reference
// top-level pointer return type to be nullable from its usage, so no other
// expectation.
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualDerivedForParamNonnull) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual void foo(int* p);
};
struct Derived : public Base {
void foo(int* p) override { *p; }
};
void target() {
int i;
Derived D;
D.foo(&i);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("Derived@F@foo")),
evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("Base@F@foo"))));
EXPECT_THAT(collectFromDefinitionNamed("Derived::foo", Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Derived@F@foo")),
evidence(paramSlot(0), Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Base@F@foo"))));
}
// Evidence for parameter nullable-ness should flow only from base to derived,
// so we collect evidence for the derived but not the base.
TEST(CollectEvidenceFromDefinitionTest, FromVirtualDerivedForParamNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual void foo(int* p);
};
struct Derived : public Base {
void foo(int* p) override { p = nullptr; }
};
void target() {
Derived D;
D.foo(nullptr);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("Derived@F@foo"))));
EXPECT_THAT(collectFromDefinitionNamed("Derived::foo", Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("Derived@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualBaseForReturnNonnull) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual int* foo() {
static int i;
return &i;
}
};
struct Derived : public Base {
int* foo() override;
};
void target() {
Base B;
*B.foo();
}
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Base::foo", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("Base@F@foo")),
evidence(SLOT_RETURN_TYPE, Evidence::NONNULL_RETURN,
functionNamed("Derived@F@foo"))));
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Base@F@foo")),
evidence(SLOT_RETURN_TYPE, Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Derived@F@foo"))));
}
// Evidence for return type nullable-ness should flow only from derived to base,
// so we collect evidence for the base but not the derived.
TEST(CollectEvidenceFromDefinitionTest, FromVirtualBaseForReturnNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual int* foo() { return nullptr; }
};
struct Derived : public Base {
int* foo() override;
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("Base::foo", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("Base@F@foo"))));
// We don't currently have any evidence kinds that can force a non-reference
// top-level pointer return type to be nullable from its usage, so no other
// expectation.
}
// Evidence for parameter nonnull-ness should flow only from derived to base, so
// we collect evidence for the base but not the derived.
TEST(CollectEvidenceFromDefinitionTest, FromVirtualBaseForParamNonnull) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual void foo(int* p) { *p; }
};
struct Derived : public Base {
void foo(int* p) override;
};
void target() {
int i;
Base B;
B.foo(&i);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("Base@F@foo"))));
EXPECT_THAT(collectFromDefinitionNamed("Base::foo", Src),
UnorderedElementsAre(evidence(paramSlot(0),
Evidence::UNCHECKED_DEREFERENCE,
functionNamed("Base@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualBaseForParamNullable) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual void foo(int* p) { p = nullptr; }
};
struct Derived : public Base {
void foo(int* p) override;
};
void target() {
Base B;
B.foo(nullptr);
}
)cc";
EXPECT_THAT(
collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("Base@F@foo")),
evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("Derived@F@foo"))));
EXPECT_THAT(collectFromDefinitionNamed("Base::foo", Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("Base@F@foo")),
evidence(paramSlot(0), Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("Derived@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualDerivedMultipleLayers) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual int* foo();
};
struct Derived : public Base {
virtual int* foo();
};
struct DerivedDerived : public Derived {
int* foo() override { return nullptr; };
};
)cc";
EXPECT_THAT(
collectFromDefinitionNamed("DerivedDerived::foo", Src),
UnorderedElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("DerivedDerived@F@foo")),
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("Derived@F@foo")),
evidence(SLOT_RETURN_TYPE, Evidence::NULLABLE_RETURN,
functionNamed("Base@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, FromVirtualBaseMultipleLayers) {
static constexpr llvm::StringRef Src = R"cc(
struct Base {
virtual void foo(int* p) { p = nullptr; }
};
struct Derived : public Base {
virtual void foo(int*);
};
struct DerivedDerived : public Derived {
void foo(int*) override;
};
)cc";
EXPECT_THAT(collectFromDefinitionNamed("Base::foo", Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("DerivedDerived@F@foo")),
evidence(paramSlot(0), Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("Derived@F@foo")),
evidence(paramSlot(0), Evidence::ASSIGNED_FROM_NULLABLE,
functionNamed("Base@F@foo"))));
}
TEST(CollectEvidenceFromDefinitionTest, NotInferenceTarget) {
static constexpr llvm::StringRef Src = R"cc(
void isATarget(Nonnull<int*> a);
template <typename T>
T* target(T* p) {
*p;
Nonnull<int*> a = p;
isATarget(p);
target<T>(nullptr);
target<int>(nullptr);
return nullptr;
}
void instantiate() { target<int>(nullptr); }
)cc";
// Doesn't collect any evidence for target from target's body, only collects
// some for isATarget.
EXPECT_THAT(
collectFromDefinitionMatching(
functionDecl(hasName("target"), isTemplateInstantiation()), Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("isATarget"))));
}
TEST(CollectEvidenceFromDefinitionTest, 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 = collectFromTargetFuncDefinition(Src);
ASSERT_THAT(FirstRoundResults, IsSupersetOf(ExpectedBothRoundResults));
for (const auto& E : ExpectedSecondRoundResults) {
ASSERT_THAT(FirstRoundResults, Not(Contains(E)));
}
EXPECT_THAT(collectFromTargetFuncDefinition(
Src, {/*Nullable=*/{fingerprint(TargetUsr, paramSlot(0))},
/*Nonnull=*/{fingerprint(TargetUsr, paramSlot(1))}}),
AllOf(IsSupersetOf(ExpectedBothRoundResults),
IsSupersetOf(ExpectedSecondRoundResults)));
}
TEST(CollectEvidenceFromDefinitionTest,
AnalysisUsesPreviousInferencesForSlotsOutsideTargetDefinition) {
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 definition 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 = collectFromTargetFuncDefinition(Src);
ASSERT_THAT(FirstRoundResults,
IsSupersetOf(ExpectedNewResultsPerRound.at(0)));
for (const auto& E : ExpectedNewResultsPerRound.at(1)) {
ASSERT_THAT(FirstRoundResults, Not(Contains(E)));
}
auto SecondRoundResults = collectFromTargetFuncDefinition(
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 = collectFromTargetFuncDefinition(
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 = collectFromTargetFuncDefinition(
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 definition, 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(CollectEvidenceFromDefinitionTest,
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 definition.
EXPECT_THAT(
collectFromTargetFuncDefinition(
Src, {.Nonnull = {fingerprint(TakesToBeNonnullUsr, paramSlot(0))}}),
Contains(evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDefinitionTest, Pragma) {
static constexpr llvm::StringRef Src = R"cc(
#pragma nullability file_default nonnull
int* target(NullabilityUnknown<int*> p) {
return p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL)));
}
TEST(CollectEvidenceFromDefinitionTest, PragmaLocalTopLevelPointer) {
static constexpr llvm::StringRef Src = R"cc(
#pragma nullability file_default nonnull
void target(NullabilityUnknown<int*> p) {
int* local_top_level_pointer = p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL)));
}
// Just confirming that the test setup to run both FrontendActions is working.
TEST(CollectEvidenceFromDefinitionTest, PragmaAndMacroReplace) {
llvm::Twine Src = CheckMacroDefinitions + R"cc(
#pragma nullability file_default nonnull
int* target(NullabilityUnknown<int*> p) {
CHECK(p);
return p;
}
)cc";
EXPECT_THAT(collectFromTargetFuncDefinition(Src.str()),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ASSIGNED_TO_NONNULL),
evidence(paramSlot(0), Evidence::ABORT_IF_NULL)));
}
TEST(CollectEvidenceFromDefinitionTest, SolverLimitReached) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* p, int* q) {
*p;
*q;
}
)cc";
NullabilityPragmas Pragmas;
clang::TestAST AST(getAugmentedTestInputs(Src, Pragmas));
std::vector<Evidence> Results;
USRCache UsrCache;
EXPECT_THAT_ERROR(
collectEvidenceFromDefinition(
*cast<FunctionDecl>(
dataflow::test::findValueDecl(AST.context(), "target")),
evidenceEmitter([&](const Evidence& E) { Results.push_back(E); },
UsrCache, AST.context()),
UsrCache, Pragmas, /*PreviousInferences=*/{},
// Enough iterations to collect one piece of evidence but not both.
[]() {
return std::make_unique<dataflow::WatchedLiteralsSolver>(
/*MaxSATIterations=*/100);
}),
llvm::FailedWithMessage("SAT solver reached iteration limit"));
EXPECT_THAT(Results, SizeIs(1));
}
TEST(CollectEvidenceFromDeclarationTest, GlobalVariable) {
llvm::StringLiteral Src = R"cc(
Nullable<int *> target;
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(Slot(0), Evidence::ANNOTATED_NULLABLE,
globalVarNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest, GlobalVariable) {
llvm::StringLiteral Src = R"cc(
#include <memory>
Nullable<std::unique_ptr<int>> target;
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(Slot(0), Evidence::ANNOTATED_NULLABLE,
globalVarNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, StaticMemberVariable) {
llvm::StringLiteral Src = R"cc(
struct S {
static Nonnull<int*> target;
};
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(Slot(0), Evidence::ANNOTATED_NONNULL,
staticFieldNamed("S::target"))));
}
TEST(CollectEvidenceFromDeclarationTest, Field) {
llvm::StringLiteral Src = R"cc(
struct S {
Nonnull<int*> target;
};
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(Slot(0), Evidence::ANNOTATED_NONNULL,
fieldNamed("S::target"))));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest, Field) {
llvm::StringLiteral Src = R"cc(
#include <memory>
struct S {
Nonnull<std::unique_ptr<int>> target;
};
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(Slot(0), Evidence::ANNOTATED_NONNULL,
fieldNamed("S::target"))));
}
TEST(CollectEvidenceFromDeclarationTest, FunctionDeclReturnType) {
llvm::StringLiteral Src = R"cc(
Nonnull<int *> target();
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
ElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::ANNOTATED_NONNULL,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, FunctionDeclParams) {
llvm::StringLiteral Src = R"cc(
void target(Nullable<int*>, int*, Nonnull<int*>);
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
ElementsAre(evidence(paramSlot(0), Evidence::ANNOTATED_NULLABLE),
evidence(paramSlot(2), Evidence::ANNOTATED_NONNULL)));
}
TEST(CollectEvidenceFromDeclarationTest, FunctionDeclNonTopLevel) {
llvm::StringLiteral Src = R"cc(
Nonnull<int*>** target(Nullable<int*>*);
)cc";
EXPECT_THAT(collectFromTargetDecl(Src), IsEmpty());
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest, FunctionDecl) {
llvm::StringLiteral Src = R"cc(
#include <memory>
Nullable<std::unique_ptr<int>> target(Nonnull<std::unique_ptr<int>>,
Nullable<std::unique_ptr<int>>);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
ElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::ANNOTATED_NULLABLE),
evidence(paramSlot(0), Evidence::ANNOTATED_NONNULL),
evidence(paramSlot(1), Evidence::ANNOTATED_NULLABLE)));
}
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(collectFromTargetDecl(Src), IsEmpty());
}
TEST(CollectEvidenceFromDeclarationTest, DefaultArgumentNullptrLiteral) {
static constexpr llvm::StringRef Src = R"cc(
void target(int* = nullptr);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, DefaultArgumentZeroLiteral) {
static constexpr llvm::StringRef Src =
R"cc(
void target(int* = 0);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, DefaultArgumentAnnotatedVariable) {
static constexpr llvm::StringRef Src = R"cc(
Nonnull<int*> q;
void target(int* = q);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NONNULL_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest,
DefaultArgumentCallingAnnotatedFunction) {
static constexpr llvm::StringRef Src = R"cc(
Nullable<int*> getDefault();
void target(int* = getDefault());
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest,
DefaultArgumentUnannotatedNonLiteralExpressionsUnknown) {
static constexpr llvm::StringRef Src = R"cc(
int* getDefault();
int* q = nullptr;
int i = 1;
void target(int* = getDefault(), int* = q, int* = &i);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("target")),
evidence(paramSlot(1), Evidence::UNKNOWN_ARGUMENT,
functionNamed("target")),
evidence(paramSlot(2), Evidence::UNKNOWN_ARGUMENT,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest,
DefaultArgumentNullptrLiteral) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void target(std::unique_ptr<int> = nullptr);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest,
DefaultArgumentMakeUniqueTooComplex) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void target(std::unique_ptr<int> = std::make_unique<int>(1));
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::UNKNOWN_ARGUMENT,
functionNamed("target"))));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest,
DefaultArgumentReferenceTypesNullptrLiteral) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void target(const std::unique_ptr<int>& pl = nullptr,
std::unique_ptr<int>&& pr = nullptr);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
UnorderedElementsAre(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
functionNamed("target")),
evidence(paramSlot(1), Evidence::NULLABLE_ARGUMENT,
functionNamed("target"))));
}
TEST(CollectEvidenceFromDeclarationTest, NonnullAttributeOnFunction) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p, int** q, int*& r, bool b) __attribute__((nonnull));
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
// attribute applies to top-level non-reference raw pointer
// parameter types only, not return type or other params.
ElementsAre(evidence(paramSlot(0), Evidence::GCC_NONNULL_ATTRIBUTE),
evidence(paramSlot(1), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest, NonnullAttributeOnFunctionWithArgs) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p, int** q, int*& r, bool b, int* not_indicated)
__attribute__((nonnull(1, 2, 3, 4)));
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
// attribute applies to the indicated and eligible parameters only.
ElementsAre(evidence(paramSlot(0), Evidence::GCC_NONNULL_ATTRIBUTE),
evidence(paramSlot(1), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest, NonnullAttributeOnMethodWithArgs) {
static constexpr llvm::StringRef Src = R"cc(
struct T {
// Index 1 on a non-static method is for the implicit `this` parameter.
int* target(int* p, int* not_indicated) __attribute__((nonnull(2)));
};
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
ElementsAre(evidence(paramSlot(0), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest,
NonnullAttributeOnStaticMethodWithArgs) {
static constexpr llvm::StringRef Src = R"cc(
struct T {
// no implicit `this` parameter for static methods.
static int* target(int* p, int* q) __attribute__((nonnull(2)));
};
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
ElementsAre(evidence(paramSlot(1), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest, NonnullAttributeOnParam) {
static constexpr llvm::StringRef Src = R"cc(
int* target(int* p __attribute__((nonnull())), int* not_indicated);
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
ElementsAre(evidence(paramSlot(0), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest,
NonnullAttributeOnFunction) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
void target(std::unique_ptr<int> p, std::unique_ptr<int>* q,
std::unique_ptr<int*> r) __attribute__((nonnull));
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
// attribute applies to top-level non-reference *raw* pointer
// parameter types only.
ElementsAre(evidence(paramSlot(1), Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest, ReturnsNonnullAttribute) {
static constexpr llvm::StringRef Src = R"cc(
int** target() __attribute__((returns_nonnull));
)cc";
EXPECT_THAT(
collectFromTargetDecl(Src),
// Affects the top-level pointer.
ElementsAre(evidence(SLOT_RETURN_TYPE, Evidence::GCC_NONNULL_ATTRIBUTE)));
}
TEST(CollectEvidenceFromDeclarationTest, ReturnsNonnullAttributeReference) {
static constexpr llvm::StringRef Src = R"cc(
int*& target() __attribute__((returns_nonnull));
)cc";
// No effect on reference types.
EXPECT_THAT(collectFromTargetDecl(Src), IsEmpty());
}
TEST(SmartPointerCollectEvidenceFromDeclarationTest, ReturnsNonnullAttribute) {
static constexpr llvm::StringRef Src = R"cc(
#include <memory>
std::unique_ptr<int> target() __attribute__((returns_nonnull));
)cc";
// No effect on smart pointers.
EXPECT_THAT(collectFromTargetDecl(Src), IsEmpty());
}
TEST(CollectEvidenceFromDeclarationTest, Pragma) {
static constexpr llvm::StringRef Src = R"cc(
#pragma nullability file_default nonnull
void target(int* p);
)cc";
EXPECT_THAT(collectFromTargetDecl(Src),
UnorderedElementsAre(
evidence(paramSlot(0), Evidence::ANNOTATED_NONNULL)));
}
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();
int* bar();
int* bar() {}
void baz(int*) {}
void def() {}
struct S {
S() {}
S(int*) {}
void member(int*);
};
void S::member(int*) {}
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(
Sites.Declarations,
UnorderedElementsAre(declNamed("bar"), declNamed("bar"), declNamed("baz"),
declNamed("S::S"), declNamed("S::member"),
declNamed("S::member")));
EXPECT_THAT(Sites.Definitions,
UnorderedElementsAre(declNamed("bar"), declNamed("baz"),
declNamed("def"), declNamed("S::S"),
declNamed("S::S"), declNamed("S::member")));
}
TEST(EvidenceSitesTest, LambdaNoPtr) {
TestAST AST(R"cc(
auto NoPtrs = []() {};
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations, IsEmpty());
EXPECT_THAT(Sites.Definitions,
UnorderedElementsAre(declNamed("(anonymous class)::operator()"),
declNamed("NoPtrs")));
}
TEST(EvidenceSitesTest, LambdaWithPtr) {
TestAST AST(R"cc(
auto Ptr = [](int*) {};
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations,
UnorderedElementsAre(declNamed("(anonymous class)::operator()")));
EXPECT_THAT(Sites.Definitions,
UnorderedElementsAre(declNamed("(anonymous class)::operator()"),
declNamed("Ptr")));
}
TEST(EvidenceSitesTest, GlobalVariables) {
NullabilityPragmas Pragmas;
TestAST AST = getAugmentedTestInputs(
R"cc(
#include <memory>
int* x = true ? nullptr : nullptr;
int* y;
int a;
int b = *y;
std::unique_ptr<int> p;
std::unique_ptr<int> q = nullptr;
)cc",
Pragmas);
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations,
UnorderedElementsAre(declNamed("x"), declNamed("y"),
declNamed("p"), declNamed("q")));
EXPECT_THAT(
Sites.Definitions,
UnorderedElementsAre(
declNamed("x"), declNamed("b"),
// unique_ptr p has an initializer because of default construction.
declNamed("p"), declNamed("q")));
}
TEST(EvidenceSitesTest, StaticMemberVariables) {
TestAST AST(R"cc(
struct S {
inline static int* a = nullptr;
static int* b;
static int* c;
};
int* S::c = nullptr;
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(
Sites.Declarations,
UnorderedElementsAre(
declNamed("S::a"), declNamed("S::b"),
// one for in-class declaration and one for out-of-class definition
declNamed("S::c"), declNamed("S::c")));
EXPECT_THAT(Sites.Definitions,
UnorderedElementsAre(declNamed("S::a"), declNamed("S::c")));
}
TEST(EvidenceSitesTest, NonStaticMemberVariables) {
NullabilityPragmas Pragmas;
TestAST AST = getAugmentedTestInputs(
R"cc(
#include <memory>
struct S {
int* a = nullptr;
int* b;
std::unique_ptr<int> p = nullptr;
std::unique_ptr<int> q;
};
)cc",
Pragmas);
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations,
UnorderedElementsAre(declNamed("S::a"), declNamed("S::b"),
declNamed("S::p"), declNamed("S::q")));
EXPECT_THAT(Sites.Definitions, IsEmpty());
}
TEST(EvidenceSitesTest, LocalVariablesNotIncluded) {
TestAST AST(R"cc(
void foo() {
int* p = nullptr;
static int* q = nullptr;
static int* r;
}
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(Sites.Declarations, IsEmpty());
EXPECT_THAT(Sites.Definitions, UnorderedElementsAre(declNamed("foo")));
}
TEST(EvidenceSitesTest, Templates) {
TestAST AST(R"cc(
template <int I>
int f(int*) {
return I;
}
template <>
int f<1>(int*) {
return 1;
}
struct S {
template <int I>
int f(int*) {
return I;
}
};
template <int I>
struct T {
int f(int*) { return I; }
};
template <int I>
int* v = nullptr;
template <>
int* v<1> = nullptr;
int Unused = f<0>(v<0>) + f<1>(v<1>) + S{}.f<0>(nullptr) + T<0>{}.f(nullptr);
)cc");
auto Sites = EvidenceSites::discover(AST.context());
// Relevant declarations are the written ones that are not templates.
EXPECT_THAT(Sites.Declarations,
UnorderedElementsAre(declNamed("f<1>"), declNamed("v<1>")));
// Instantiations are relevant definitions, as is the global variable Unused.
EXPECT_THAT(Sites.Definitions,
UnorderedElementsAre(declNamed("f<0>"), declNamed("v<0>"),
declNamed("f<1>"), declNamed("v<1>"),
declNamed("S::f<0>"), declNamed("T<0>::f"),
declNamed("Unused")));
}
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 USRCache;
EXPECT_DEATH(
evidenceEmitter([](const Evidence& E) {}, USRCache, AST.context())(
*TargetDecl, Slot{}, Evidence::ANNOTATED_UNKNOWN,
TargetDecl->getLocation()),
"not an inference target");
}
} // namespace
} // namespace clang::tidy::nullability