[nullability] Smart pointers: Support `absl_nullability_compatible` tag.
This allows tagging user-defined smart pointer types as supporting nullability
annotations.
PiperOrigin-RevId: 601341438
Change-Id: I48ab21128dc6b90f080bb8ae7697e0773e647fdd
diff --git a/bazel/llvm.bzl b/bazel/llvm.bzl
index 0064a25..fcd7f41 100644
--- a/bazel/llvm.bzl
+++ b/bazel/llvm.bzl
@@ -53,7 +53,7 @@
executable = False,
)
-LLVM_COMMIT_SHA = "c416b2efe89c11db593fe8041c366e0cb63d4eeb"
+LLVM_COMMIT_SHA = "98509c7f9792c79b05a41b95c24607f6dd489c5a"
def llvm_loader_repository_dependencies():
# This *declares* the dependency, but it won't actually be *downloaded* unless it's used.
diff --git a/nullability/test/smart_pointers.cc b/nullability/test/smart_pointers.cc
index 67f083e..387446a 100644
--- a/nullability/test/smart_pointers.cc
+++ b/nullability/test/smart_pointers.cc
@@ -536,3 +536,32 @@
std::weak_ptr<int> Weak(Shared);
nullable(Weak.lock());
}
+
+namespace user_defined_smart_pointers {
+
+template <typename T>
+struct UserDefinedSmartPointer {
+ using absl_nullability_compatible = void;
+ using pointer = T *;
+
+ pointer get() const;
+};
+
+TEST void userDefinedSmartPointers(
+ Nonnull<UserDefinedSmartPointer<int>> NonnullParam,
+ Nullable<UserDefinedSmartPointer<int>> NullableParam,
+ UserDefinedSmartPointer<int> UnknownParam) {
+ // Just spot-check some basic behaviors, as the implementation treats
+ // user-defined smart pointers like standard smart pointers, so the tests for
+ // standard smart pointers provide sufficient coverage.
+
+ nonnull(NonnullParam);
+ nullable(NullableParam);
+ unknown(UnknownParam);
+
+ nonnull(NonnullParam.get());
+ nullable(NullableParam.get());
+ unknown(UnknownParam.get());
+}
+
+} // namespace user_defined_smart_pointers
diff --git a/nullability/type_nullability.cc b/nullability/type_nullability.cc
index 2d1aebf..18b2534 100644
--- a/nullability/type_nullability.cc
+++ b/nullability/type_nullability.cc
@@ -52,20 +52,37 @@
return !underlyingRawPointerType(T).isNull();
}
+static bool isStandardSmartPointerDecl(const CXXRecordDecl *RD) {
+ if (!RD->getDeclContext()->isStdNamespace()) return false;
+
+ const IdentifierInfo *ID = RD->getIdentifier();
+ if (ID == nullptr) return false;
+
+ StringRef Name = ID->getName();
+ return Name == "unique_ptr" || Name == "shared_ptr";
+}
+
+static bool hasAbslNullabilityCompatibleTag(const CXXRecordDecl *RD) {
+ // If the specialization hasn't been instantiated -- for example because it
+ // is only used as a function parameter type -- then the specialization won't
+ // contain the `absl_nullability_compatible` tag. Therefore, we look at the
+ // template rather than the specialization.
+ if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+ RD = CTSD->getSpecializedTemplate()->getTemplatedDecl();
+ const auto &Idents = RD->getASTContext().Idents;
+ auto It = Idents.find("absl_nullability_compatible");
+ if (It == Idents.end()) return false;
+ return RD->lookup(It->getValue()).find_first<TypedefNameDecl>() != nullptr;
+}
+
QualType underlyingRawPointerType(QualType T) {
if (!SmartPointersEnabled) return QualType();
- // TODO(b/304963199): Add support for the `absl_nullability_compatible` tag.
const CXXRecordDecl *RD = T.getCanonicalType()->getAsCXXRecordDecl();
if (RD == nullptr) return QualType();
- if (!RD->getDeclContext()->isStdNamespace()) return QualType();
-
- const IdentifierInfo *ID = RD->getIdentifier();
- if (ID == nullptr) return QualType();
-
- StringRef Name = ID->getName();
- if (Name != "unique_ptr" && Name != "shared_ptr") return QualType();
+ if (!isStandardSmartPointerDecl(RD) && !hasAbslNullabilityCompatibleTag(RD))
+ return QualType();
const ASTContext &ASTCtx = RD->getASTContext();
const auto &Idents = ASTCtx.Idents;
diff --git a/nullability/type_nullability_test.cc b/nullability/type_nullability_test.cc
index b530708..20bdda3 100644
--- a/nullability/type_nullability_test.cc
+++ b/nullability/type_nullability_test.cc
@@ -95,6 +95,12 @@
using SugaredPointer = UniquePointer;
+ template <typename T>
+ struct UserDefinedSmartPointer {
+ using absl_nullability_compatible = void;
+ };
+ using UserDefined = UserDefinedSmartPointer<NotPointer>;
+
template <class>
struct Container;
using ContainsPointers = Container<std::unique_ptr<int>>;
@@ -107,6 +113,7 @@
EXPECT_FALSE(isSupportedSmartPointerType(
underlying("UniquePointerWrongNamespace", AST)));
EXPECT_TRUE(isSupportedSmartPointerType(underlying("SugaredPointer", AST)));
+ EXPECT_TRUE(isSupportedSmartPointerType(underlying("UserDefined", AST)));
EXPECT_FALSE(
isSupportedSmartPointerType(underlying("ContainsPointers", AST)));
}
@@ -131,14 +138,22 @@
};
} // namespace std
+ template <typename T>
+ struct UserDefinedSmartPointer {
+ using absl_nullability_compatible = void;
+ using pointer = char *;
+ };
+
using UniquePointer = std::unique_ptr<int>;
using SharedPointer = std::shared_ptr<int>;
+ using UserDefined = UserDefinedSmartPointer<int>;
// Force the compiler to instantiate the templates. Otherwise, the
// `ClassTemplateSpecializationDecl` won't contain a `TypedefNameDecl` for
// `pointer` or `element_type`, and `underlyingRawPointerType()` will
// use the fallback behavior of looking at the template argument.
template class std::unique_ptr<int>;
template class std::shared_ptr<int>;
+ template class UserDefinedSmartPointer<int>;
)cpp");
QualType PointerToCharTy = AST.context().getPointerType(AST.context().CharTy);
@@ -146,6 +161,8 @@
PointerToCharTy);
EXPECT_EQ(underlyingRawPointerType(underlying("SharedPointer", AST)),
PointerToCharTy);
+ EXPECT_EQ(underlyingRawPointerType(underlying("UserDefined", AST)),
+ PointerToCharTy);
}
TEST_F(UnderlyingRawPointerTest, NotInstantiated) {