Change requirements for inference targets and slot counting.
Functions that have no pointers present in their type will no longer be targets. We will still collect evidence from non-target definitions.
Variables and fields that have pointer-containing types but are not simply a raw or smart pointer will now be considered targets. However, these pointer types within a more complex type will not be counted as inferable slots, as we currently attempt no inference for them.
Also properly exclude variable template specializations.
PiperOrigin-RevId: 651445869
Change-Id: I434ce133d1e3a69d0f7ff591ca6cd57d900163c8
diff --git a/nullability/inference/collect_evidence_test.cc b/nullability/inference/collect_evidence_test.cc
index cdd3643..195e3eb 100644
--- a/nullability/inference/collect_evidence_test.cc
+++ b/nullability/inference/collect_evidence_test.cc
@@ -3466,37 +3466,51 @@
TEST(EvidenceSitesTest, Functions) {
TestAST AST(R"cc(
void foo();
- void bar();
- void bar() {}
- void baz() {}
+ int* bar();
+ int* bar() {}
+ void baz(int*) {}
+ void def() {}
struct S {
S() {}
- void member();
+ S(int*) {}
+ void member(int*);
};
- void S::member() {}
+ void S::member(int*) {}
)cc");
auto Sites = EvidenceSites::discover(AST.context());
EXPECT_THAT(
Sites.Declarations,
- UnorderedElementsAre(declNamed("foo"), declNamed("bar"), declNamed("bar"),
- declNamed("baz"), declNamed("S::S"),
- declNamed("S::member"), declNamed("S::member")));
+ 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, Lambdas) {
+TEST(EvidenceSitesTest, LambdaNoPtr) {
TestAST AST(R"cc(
- auto Lambda = []() {};
+ 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("Lambda")));
+ declNamed("Ptr")));
}
TEST(EvidenceSitesTest, GlobalVariables) {
@@ -3571,60 +3585,56 @@
void foo() {
int* p = nullptr;
static int* q = nullptr;
+ static int* r;
}
)cc");
auto Sites = EvidenceSites::discover(AST.context());
- EXPECT_THAT(Sites.Declarations, UnorderedElementsAre(declNamed("foo")));
+ 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 f(int*) {
return I;
}
template <>
- int f<1>() {
+ int f<1>(int*) {
return 1;
}
struct S {
template <int I>
- int f() {
+ int f(int*) {
return I;
}
};
template <int I>
struct T {
- int f() { return I; }
+ int f(int*) { return I; }
};
template <int I>
- int v = 1;
+ int* v = nullptr;
- int Unused = f<0>() + f<1>() + S{}.f<0>() + T<0>{}.f() + v<0>;
+ 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>")));
+ 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("f<1>"),
+ UnorderedElementsAre(declNamed("f<0>"), declNamed("v<0>"),
+ declNamed("f<1>"), declNamed("v<1>"),
declNamed("S::f<0>"), declNamed("T<0>::f"),
- declNamed("v<0>"), declNamed("Unused")));
-
- for (auto* Def : Sites.Definitions) {
- std::string Actual;
- llvm::raw_string_ostream OS(Actual);
- if (auto* ND = dyn_cast<NamedDecl>(Def))
- ND->getNameForDiagnostic(
- OS, Def->getDeclContext()->getParentASTContext().getPrintingPolicy(),
- /*Qualified=*/true);
- llvm::errs() << "Actual: " << Actual << "\n";
- }
+ declNamed("Unused")));
}
TEST(EvidenceEmitterTest, NotInferenceTarget) {
diff --git a/nullability/inference/inferable.cc b/nullability/inference/inferable.cc
index 91c268b..ac131e2 100644
--- a/nullability/inference/inferable.cc
+++ b/nullability/inference/inferable.cc
@@ -26,10 +26,10 @@
return Slots;
}
if (const auto* Field = dyn_cast<FieldDecl>(&D)) {
- return isInferenceTarget(*Field) ? 1 : 0;
+ return hasInferable(Field->getType()) ? 1 : 0;
}
if (const auto* Var = dyn_cast<VarDecl>(&D)) {
- return isInferenceTarget(*Var) ? 1 : 0;
+ return hasInferable(Var->getType()) ? 1 : 0;
}
return 0;
}
@@ -57,24 +57,28 @@
// small set of functions.
Func->getBuiltinID() == 0 &&
// Implicit functions cannot be annotated.
- !Func->isImplicit();
+ !Func->isImplicit() &&
+ // Do the most expensive check last.
+ countPointersInType(Func->getType()) > 0;
}
if (const auto* Field = dyn_cast<FieldDecl>(&D)) {
- return hasInferable(Field->getType()) &&
- // See comments above regarding dependent contexts and templates.
- !Field->getDeclContext()->isDependentContext() &&
- !isa<ClassTemplateSpecializationDecl>(Field->getParent());
+ return
+ // See comments above regarding dependent contexts and templates.
+ !Field->getDeclContext()->isDependentContext() &&
+ !isa<ClassTemplateSpecializationDecl>(Field->getParent()) &&
+ // Do the most expensive check last.
+ countPointersInType(Field->getType()) > 0;
}
if (const auto* Var = dyn_cast<VarDecl>(&D)) {
// Include static member variables and global variables, but not static
// local variables. Local variables often do not need annotation in order to
// be verified.
- return hasInferable(Var->getType()) && Var->hasGlobalStorage() &&
- !Var->isStaticLocal() &&
+ return Var->hasGlobalStorage() && !Var->isStaticLocal() &&
// See comments above regarding dependent contexts and templates.
!Var->getDeclContext()->isDependentContext() &&
- !Var->isTemplated() &&
- !isa<ClassTemplateSpecializationDecl>(Var->getDeclContext());
+ !Var->isTemplated() && !Var->getTemplateInstantiationPattern() &&
+ // Do the most expensive check last.
+ countPointersInType(Var->getType()) > 0;
}
return false;
}
diff --git a/nullability/inference/inferable_test.cc b/nullability/inference/inferable_test.cc
index ca920a5..1ab2484 100644
--- a/nullability/inference/inferable_test.cc
+++ b/nullability/inference/inferable_test.cc
@@ -10,6 +10,7 @@
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
@@ -93,6 +94,7 @@
}
void empty() {}
auto Lambda = []() {};
+ auto LambdaWithPtr = [](int*) {};
)cc")
.str());
@@ -102,15 +104,23 @@
EXPECT_FALSE(isInferenceTarget(lookup("StaticLocal", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("StdSmartLocal", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("CustomSmartLocal", Ctx)));
- EXPECT_TRUE(isInferenceTarget(lookup("empty", Ctx)));
- EXPECT_FALSE(isInferenceTarget(lookup("Lambda", Ctx)));
- EXPECT_TRUE(isInferenceTarget(lookup("operator()", Ctx)));
+ EXPECT_FALSE(isInferenceTarget(lookup("empty", Ctx)));
+ auto &Lambda = lookup<VarDecl>("Lambda", Ctx);
+ auto *LambdaCtx = cast<LambdaExpr>(Lambda.getInit())->getLambdaClass();
+ EXPECT_FALSE(isInferenceTarget(Lambda));
+ EXPECT_FALSE(isInferenceTarget(lookup("operator()", Ctx, LambdaCtx)));
+ auto &LambdaWithPtr = lookup<VarDecl>("LambdaWithPtr", Ctx);
+ auto *LambdaWithPtrCtx =
+ cast<LambdaExpr>(LambdaWithPtr.getInit())->getLambdaClass();
+ EXPECT_FALSE(isInferenceTarget(LambdaWithPtr));
+ EXPECT_TRUE(isInferenceTarget(lookup("operator()", Ctx, LambdaWithPtrCtx)));
}
TEST(IsInferenceTargetTest, ClassAndMembers) {
TestAST AST((SmartPointerHeader + R"cc(
class C {
void method();
+ int* methodWithPtr();
int NonPtrField;
int* PtrField;
static int* StaticField;
@@ -122,7 +132,8 @@
auto &Ctx = AST.context();
EXPECT_FALSE(isInferenceTarget(lookup<CXXRecordDecl>("C", Ctx)));
- EXPECT_TRUE(isInferenceTarget(lookup("method", Ctx)));
+ EXPECT_FALSE(isInferenceTarget(lookup("method", Ctx)));
+ EXPECT_TRUE(isInferenceTarget(lookup("methodWithPtr", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("NonPtrField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("PtrField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StaticField", Ctx)));
@@ -145,11 +156,11 @@
EXPECT_FALSE(isInferenceTarget(FuncTmpl));
EXPECT_FALSE(isInferenceTarget(*FuncTmpl.getTemplatedDecl()));
// The function template specialization is *also* not an inference target.
- const VarDecl &FuncTmplSpec = lookup<VarDecl>("FuncTmplSpec", Ctx);
- EXPECT_FALSE(isInferenceTarget(FuncTmplSpec));
- const ValueDecl &FuncTmpl2 =
- *cast<DeclRefExpr>(FuncTmplSpec.getInit()->IgnoreImplicit())->getDecl();
- EXPECT_FALSE(isInferenceTarget(FuncTmpl2));
+ const ValueDecl &Specialization =
+ *cast<DeclRefExpr>(
+ lookup<VarDecl>("FuncTmplSpec", Ctx).getInit()->IgnoreImplicit())
+ ->getDecl();
+ EXPECT_FALSE(isInferenceTarget(Specialization));
}
TEST(IsInferenceTargetTest, ClassTemplateAndMembers) {