Extract a few functions for reuse in inference pipelines
Also create isSupportedPointer() instead of inlining it in yet another place.
Haven't migrating current (should-be) callers, though.
PiperOrigin-RevId: 551859905
Change-Id: I8d314e46a2cc6ccfadb1b1804e05d14ffc05defc
diff --git a/nullability/BUILD b/nullability/BUILD
index 5a6b904..ae4b293 100644
--- a/nullability/BUILD
+++ b/nullability/BUILD
@@ -142,6 +142,7 @@
deps = [
":type_nullability",
"@absl//absl/log:check",
+ "@llvm-project//clang:ast",
"@llvm-project//clang:basic",
"@llvm-project//clang:testing",
"@llvm-project//llvm:Support",
diff --git a/nullability/inference/BUILD b/nullability/inference/BUILD
index f15a4da..a61b978 100644
--- a/nullability/inference/BUILD
+++ b/nullability/inference/BUILD
@@ -10,6 +10,7 @@
hdrs = ["collect_evidence.h"],
deps = [
":inference_cc_proto",
+ ":inferrable",
"//nullability:pointer_nullability",
"//nullability:pointer_nullability_analysis",
"//nullability:pointer_nullability_lattice",
@@ -67,6 +68,34 @@
)
cc_library(
+ name = "inferrable",
+ srcs = ["inferrable.cc"],
+ hdrs = ["inferrable.h"],
+ deps = [
+ "//nullability:type_nullability",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:basic",
+ ],
+)
+
+cc_test(
+ name = "inferrable_test",
+ srcs = ["inferrable_test.cc"],
+ deps = [
+ ":inference_cc_proto",
+ ":inferrable",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:basic",
+ "@llvm-project//clang:testing",
+ "@llvm-project//clang/unittests:dataflow_testing_support",
+ "@llvm-project//llvm:Support",
+ "@llvm-project//third-party/unittest:gmock",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_library(
name = "infer_tu",
srcs = ["infer_tu.cc"],
hdrs = ["infer_tu.h"],
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index 673067b..6cd8165 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -10,6 +10,7 @@
#include <vector>
#include "nullability/inference/inference.proto.h"
+#include "nullability/inference/inferrable.h"
#include "nullability/pointer_nullability.h"
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/pointer_nullability_lattice.h"
@@ -87,21 +88,6 @@
}
namespace {
-bool isInferenceTarget(const FunctionDecl &FD) {
- return
- // Function templates are in principle inferrable.
- // However since we don't analyze their bodies, and other implementations
- // cannot interact with them directly, we can't perform any nontrivial
- // inference, just propagate annotations across redecls.
- // For now, we don't do this as some infra (NullabilityWalker) doesn't
- // work on dependent code.
- !FD.isDependentContext() &&
- // Inferring properties of template instantiations isn't useful in
- // itself. We can't record them anywhere unless they apply to the
- // template in general.
- // TODO: work out in what circumstances that would be safe.
- !FD.getTemplateInstantiationPattern();
-}
void collectEvidenceFromDereference(
std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableSlots,
diff --git a/nullability/inference/inferrable.cc b/nullability/inference/inferrable.cc
new file mode 100644
index 0000000..2adb12a
--- /dev/null
+++ b/nullability/inference/inferrable.cc
@@ -0,0 +1,52 @@
+// 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/inferrable.h"
+
+#include "nullability/type_nullability.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/LLVM.h"
+
+namespace clang::tidy::nullability {
+
+namespace {
+
+bool isInferrable(QualType T) {
+ return isSupportedPointer(T.getNonReferenceType().getCanonicalType());
+}
+
+} // namespace
+
+int countInferrableSlots(const Decl& D) {
+ const clang::FunctionDecl* Func = dyn_cast<clang::FunctionDecl>(&D);
+ if (!Func) return 0;
+ int Slots = 0;
+ if (isInferrable(Func->getReturnType())) ++Slots;
+ for (auto* P : Func->parameters())
+ if (isInferrable(P->getType())) ++Slots;
+ return Slots;
+}
+
+bool isInferenceTarget(const Decl& D) {
+ // For now, only support inferring nullability of functions.
+ const auto* FD = dyn_cast<FunctionDecl>(&D);
+ if (!FD) return false;
+ return
+ // Function templates are in principle inferrable.
+ // However since we don't analyze their bodies, and other implementations
+ // cannot interact with them directly, we can't perform any nontrivial
+ // inference, just propagate annotations across redecls.
+ // For now, we don't do this as some infra (NullabilityWalker) doesn't
+ // work on dependent code.
+ !FD->isDependentContext() &&
+ // Inferring properties of template instantiations isn't useful in
+ // itself. We can't record them anywhere unless they apply to the
+ // template in general.
+ // TODO: work out in what circumstances that would be safe.
+ !FD->getTemplateInstantiationPattern();
+}
+
+} // namespace clang::tidy::nullability
diff --git a/nullability/inference/inferrable.h b/nullability/inference/inferrable.h
new file mode 100644
index 0000000..552fe6a
--- /dev/null
+++ b/nullability/inference/inferrable.h
@@ -0,0 +1,23 @@
+// 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
+
+#ifndef CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
+#define CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
+
+#include "clang/AST/DeclBase.h"
+
+namespace clang::tidy::nullability {
+
+/// Should we attempt to deduce nullability for this symbol?
+bool isInferenceTarget(const Decl &);
+
+/// The number of nullability slots in this symbol's type which can be inferred.
+///
+/// This may not be all the slots in the type: e.g. `int** X` has outer and
+/// inner nullability, we may support only inferring outer.
+int countInferrableSlots(const clang::Decl &);
+
+} // namespace clang::tidy::nullability
+
+#endif // THIRD_PARTY_CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
diff --git a/nullability/inference/inferrable_test.cc b/nullability/inference/inferrable_test.cc
new file mode 100644
index 0000000..e906203
--- /dev/null
+++ b/nullability/inference/inferrable_test.cc
@@ -0,0 +1,108 @@
+// 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/inferrable.h"
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Testing/TestAST.h"
+#include "llvm/ADT/StringRef.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang::tidy::nullability {
+namespace {
+
+template <class T = NamedDecl>
+const T &lookup(llvm::StringRef Name, const DeclContext &DC) {
+ auto L = DC.lookup(&DC.getParentASTContext().Idents.get(Name));
+ EXPECT_TRUE(L.isSingleResult()) << Name;
+ auto *Result = L.find_first<T>();
+ EXPECT_NE(Result, nullptr) << Name;
+ return *Result;
+}
+
+TEST(InferrableTest, IsInferenceTarget) {
+ TestAST AST(R"cc(
+ int* Pointer;
+ int* func(int*, int**);
+ void empty() {}
+
+ class Cls {
+ void method();
+ };
+
+ template <int X>
+ void funcTmpl(int*) {}
+
+ auto& FuncTmplSpec = funcTmpl<2>;
+ )cc");
+
+ auto &TU = *AST.context().getTranslationUnitDecl();
+ EXPECT_FALSE(isInferenceTarget(lookup("Pointer", TU)));
+ EXPECT_TRUE(isInferenceTarget(lookup("func", TU)));
+ EXPECT_TRUE(isInferenceTarget(lookup("empty", TU)));
+
+ auto &Cls = lookup<CXXRecordDecl>("Cls", TU);
+ EXPECT_FALSE(isInferenceTarget(Cls));
+ EXPECT_TRUE(isInferenceTarget(lookup("method", Cls)));
+
+ // A function template is not an inference target.
+ const FunctionTemplateDecl &FuncTmpl =
+ lookup<FunctionTemplateDecl>("funcTmpl", TU);
+ EXPECT_FALSE(isInferenceTarget(FuncTmpl));
+ EXPECT_FALSE(isInferenceTarget(*FuncTmpl.getTemplatedDecl()));
+ // The template specialization is *also* not an inference target.
+ const VarDecl &FuncTmplSpec = lookup<VarDecl>("FuncTmplSpec", TU);
+ EXPECT_FALSE(isInferenceTarget(FuncTmplSpec));
+ const ValueDecl &FuncTmpl2 =
+ *cast<DeclRefExpr>(FuncTmplSpec.getInit()->IgnoreImplicit())->getDecl();
+ EXPECT_FALSE(isInferenceTarget(FuncTmpl2));
+}
+
+TEST(InferrableTest, CountInferrableSlots) {
+ TestAST AST(R"cc(
+ using Pointer = int *;
+ template <class T>
+ struct S;
+ struct T;
+
+ void f1(int *);
+ void f2(Pointer);
+ void f3(int **);
+ void f4(Pointer *);
+ void f5(int *&);
+ void f6(int (*)()); // function pointer
+
+ int *g1(int);
+ Pointer g2(int);
+
+ void h1(S<int *>);
+ void h2(int T::*); // pointer to data member
+ void h3(int (T::*)()); // pointer to member function
+ )cc");
+ auto &TU = *AST.context().getTranslationUnitDecl();
+
+ // All the 'f's have a single pointer arg.
+ EXPECT_EQ(1, countInferrableSlots(lookup("f1", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("f2", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("f3", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("f4", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("f5", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("f6", TU)));
+
+ // All the 'g's have a pointer return.
+ EXPECT_EQ(1, countInferrableSlots(lookup("g1", TU)));
+ EXPECT_EQ(1, countInferrableSlots(lookup("g2", TU)));
+
+ // The 'h's have types that aren't really pointers.
+ EXPECT_EQ(0, countInferrableSlots(lookup("h1", TU)));
+ EXPECT_EQ(0, countInferrableSlots(lookup("h2", TU)));
+ EXPECT_EQ(0, countInferrableSlots(lookup("h3", TU)));
+}
+
+} // namespace
+} // namespace clang::tidy::nullability
diff --git a/nullability/type_nullability.cc b/nullability/type_nullability.cc
index ae6d6b4..c5c4f18 100644
--- a/nullability/type_nullability.cc
+++ b/nullability/type_nullability.cc
@@ -24,6 +24,8 @@
namespace clang::tidy::nullability {
+bool isSupportedPointer(QualType T) { return llvm::isa<PointerType>(T); }
+
PointerTypeNullability PointerTypeNullability::createSymbolic(
dataflow::Arena &A) {
PointerTypeNullability Symbolic;
diff --git a/nullability/type_nullability.h b/nullability/type_nullability.h
index 3c753dd..2a68815 100644
--- a/nullability/type_nullability.h
+++ b/nullability/type_nullability.h
@@ -32,6 +32,7 @@
#include "absl/log/check.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/Type.h"
#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Basic/LLVM.h"
@@ -39,6 +40,13 @@
namespace clang::tidy::nullability {
+/// Is this exactly a pointer type that we track outer nullability for?
+/// Does not unwrap sugar, consider isSupportedPointer(T.getCanonicalType()).
+///
+/// (For now, only regular PointerTypes, in future we should consider supporting
+/// pointer-to-member, ObjC pointers, etc).
+bool isSupportedPointer(QualType);
+
/// Describes the nullability contract of a pointer "slot" within a type.
///
/// This may be concrete: nullable/non-null/unknown nullability.
diff --git a/nullability/type_nullability_test.cc b/nullability/type_nullability_test.cc
index f10f2fc..7306282 100644
--- a/nullability/type_nullability_test.cc
+++ b/nullability/type_nullability_test.cc
@@ -5,6 +5,7 @@
#include "nullability/type_nullability.h"
#include "absl/log/check.h"
+#include "clang/AST/Decl.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
@@ -15,6 +16,41 @@
namespace {
using testing::ElementsAre;
+TEST(TypeNullabilityTest, IsSupportedPointer) {
+ TestAST AST(R"cpp(
+ using NotPointer = int;
+ using Pointer = NotPointer*;
+ using FuncPointer = Pointer (*)(Pointer);
+ using SugaredPointer = Pointer;
+
+ struct S;
+ using PointerDataMember = Pointer S::*;
+ using PointerMemberFunction = Pointer (S::*)(Pointer);
+
+ @class X;
+ using ObjCPointer = X;
+
+ template <class>
+ struct Container;
+ using ContainsPointers = Container<int*>;
+ )cpp");
+
+ auto Underlying = [&](llvm::StringRef Name) {
+ auto Lookup = AST.context().getTranslationUnitDecl()->lookup(
+ &AST.context().Idents.get(Name));
+ EXPECT_TRUE(Lookup.isSingleResult());
+ return Lookup.find_first<TypeAliasDecl>()->getUnderlyingType();
+ };
+ EXPECT_FALSE(isSupportedPointer(Underlying("NotPointer")));
+ EXPECT_TRUE(isSupportedPointer(Underlying("Pointer")));
+ EXPECT_TRUE(isSupportedPointer(Underlying("FuncPointer")));
+ EXPECT_FALSE(isSupportedPointer(Underlying("SugaredPointer")));
+ EXPECT_FALSE(isSupportedPointer(Underlying("PointerDataMember")));
+ EXPECT_FALSE(isSupportedPointer(Underlying("PointerMemberFunction")));
+ EXPECT_FALSE(isSupportedPointer(Underlying("ObjCPointer")));
+ EXPECT_FALSE(isSupportedPointer(Underlying("ContainsPointers")));
+}
+
class GetNullabilityAnnotationsFromTypeTest : public ::testing::Test {
protected:
// C++ declarations prepended before parsing type in nullVec().