https://github.com/sam-mccall updated https://github.com/llvm/llvm-project/pull/82705
>From eccc46840e343e7ba15200cd4b81316a51c46943 Mon Sep 17 00:00:00 2001 From: Sam McCall <sam.mcc...@gmail.com> Date: Thu, 22 Feb 2024 16:00:44 +0100 Subject: [PATCH 1/2] [clang][nullability] allow _Nonnull etc on nullable class types This enables clang and external nullability checkers to make use of these annotations on nullable C++ class types like unique_ptr. These types are recognized by the presence of the _Nullable attribute. Nullable standard library types implicitly receive this attribute. Existing static warnings for raw pointers are extended to smart pointers: - nullptr used as return value or argument for non-null functions (`-Wnonnull`) - assigning or initializing nonnull variables with nullable values (`-Wnullable-to-nonnull-conversion`) It doesn't implicitly add these attributes based on the assume_nonnull pragma, nor warn on missing attributes where the pragma would apply them. I'm not confident that the pragma's current behavior will work well for C++ (where type-based metaprogramming is much more common than C/ObjC). We'd like to revisit this once we have more implementation experience. Support can be detected as `__has_feature(nullability_on_classes)`. This is needed for back-compatibility, as previously clang would issue a hard error when _Nullable appears on a smart pointer. UBSan's `-fsanitize=nullability` will not check smart-pointer types. It can be made to do so by synthesizing calls to `operator bool`, but that's left for future work. --- clang/include/clang/Basic/Attr.td | 3 +- clang/include/clang/Basic/AttrDocs.td | 16 +++++++ clang/include/clang/Basic/Features.def | 1 + clang/include/clang/Parse/Parser.h | 1 + clang/include/clang/Sema/Sema.h | 3 ++ clang/lib/AST/Type.cpp | 29 +++++++----- clang/lib/CodeGen/CGCall.cpp | 3 +- clang/lib/CodeGen/CodeGenFunction.cpp | 3 +- clang/lib/Parse/ParseDeclCXX.cpp | 33 ++++++++++---- clang/lib/Sema/SemaAttr.cpp | 12 +++++ clang/lib/Sema/SemaChecking.cpp | 9 ++++ clang/lib/Sema/SemaDecl.cpp | 4 +- clang/lib/Sema/SemaDeclAttr.cpp | 18 ++++++++ clang/lib/Sema/SemaInit.cpp | 5 +++ clang/lib/Sema/SemaOverload.cpp | 7 +++ clang/lib/Sema/SemaTemplate.cpp | 1 + clang/lib/Sema/SemaType.cpp | 18 ++++++-- clang/test/SemaCXX/nullability.cpp | 62 +++++++++++++++++++++++++- 18 files changed, 199 insertions(+), 29 deletions(-) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index fa191c7378dba4..f75aa126cbb939 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2156,9 +2156,10 @@ def TypeNonNull : TypeAttr { let Documentation = [TypeNonNullDocs]; } -def TypeNullable : TypeAttr { +def TypeNullable : DeclOrTypeAttr { let Spellings = [CustomKeyword<"_Nullable">]; let Documentation = [TypeNullableDocs]; +// let Subjects = SubjectList<[CXXRecord], ErrorDiag>; } def TypeNullableResult : TypeAttr { diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b96fbddd51154c..8c248fb052f19e 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4096,6 +4096,11 @@ non-underscored keywords. For example: @property (assign, nullable) NSView *superview; @property (readonly, nonnull) NSArray *subviews; @end + +As well as built-in pointer types, ithe nullability attributes can be attached +to nullable types from the C++ standard library such as ``std::unique_ptr`` and +``std::function``, as well as C++ classes marked with the ``_Nullable`` +attribute. }]; } @@ -4130,6 +4135,17 @@ The ``_Nullable`` nullability qualifier indicates that a value of the int fetch_or_zero(int * _Nullable ptr); a caller of ``fetch_or_zero`` can provide null. + +The ``_Nullable`` attribute on classes indicates that the given class can +represent null values, and so the ``_Nullable``, ``_Nonnull`` etc qualifiers +make sense for this type. For example: + + .. code-block:: c + + class _Nullable ArenaPointer { ... }; + + ArenaPointer _Nonnull x = ...; + ArenaPointer _Nullable y = nullptr; }]; } diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def index 5fad5fc3623cb6..7c0755b7318306 100644 --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -94,6 +94,7 @@ EXTENSION(define_target_os_macros, FEATURE(enumerator_attributes, true) FEATURE(nullability, true) FEATURE(nullability_on_arrays, true) +FEATURE(nullability_on_classes, true) FEATURE(nullability_nullable_result, true) FEATURE(memory_sanitizer, LangOpts.Sanitize.hasOneOf(SanitizerKind::Memory | diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 071520f535bc95..3d85868b69d580 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -3007,6 +3007,7 @@ class Parser : public CodeCompletionHandler { void DiagnoseAndSkipExtendedMicrosoftTypeAttributes(); SourceLocation SkipExtendedMicrosoftTypeAttributes(); void ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs); + void ParseNullabilityClassAttributes(ParsedAttributes &attrs); void ParseBorlandTypeAttributes(ParsedAttributes &attrs); void ParseOpenCLKernelAttributes(ParsedAttributes &attrs); void ParseOpenCLQualifiers(ParsedAttributes &Attrs); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fcccac10f4733a..9511cf58f16327 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8004,6 +8004,9 @@ class Sema final { /// Add [[gsl::Pointer]] attributes for std:: types. void inferGslPointerAttribute(TypedefNameDecl *TD); + /// Add _Nullable attributes for std:: types. + void inferNullableClassAttribute(CXXRecordDecl *CRD); + void CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record); /// Check that the C++ class annoated with "trivial_abi" satisfies all the diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 78dcd3f4007a5a..bb0a5e7bc59e9e 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -4556,16 +4556,15 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const { case Type::Auto: return ResultIfUnknown; - // Dependent template specializations can instantiate to pointer - // types unless they're known to be specializations of a class - // template. + // Dependent template specializations could instantiate to pointer types. case Type::TemplateSpecialization: - if (TemplateDecl *templateDecl - = cast<TemplateSpecializationType>(type.getTypePtr()) - ->getTemplateName().getAsTemplateDecl()) { - if (isa<ClassTemplateDecl>(templateDecl)) - return false; - } + // If it's a known class template, we can already check if it's nullable. + if (TemplateDecl *templateDecl = + cast<TemplateSpecializationType>(type.getTypePtr()) + ->getTemplateName() + .getAsTemplateDecl()) + if (auto *CTD = dyn_cast<ClassTemplateDecl>(templateDecl)) + return CTD->getTemplatedDecl()->hasAttr<TypeNullableAttr>(); return ResultIfUnknown; case Type::Builtin: @@ -4622,6 +4621,17 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const { } llvm_unreachable("unknown builtin type"); + case Type::Record: { + const RecordDecl *RD = cast<RecordType>(type)->getDecl(); + // For template specializations, look only at primary template attributes. + // This is a consistent regardless of whether the instantiation is known. + if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) + return CTSD->getSpecializedTemplate() + ->getTemplatedDecl() + ->hasAttr<TypeNullableAttr>(); + return RD->hasAttr<TypeNullableAttr>(); + } + // Non-pointer types. case Type::Complex: case Type::LValueReference: @@ -4639,7 +4649,6 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const { case Type::DependentAddressSpace: case Type::FunctionProto: case Type::FunctionNoProto: - case Type::Record: case Type::DeducedTemplateSpecialization: case Type::Enum: case Type::InjectedClassName: diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index d05cf1c6e1814e..83cfc8831b036c 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -4373,7 +4373,8 @@ void CodeGenFunction::EmitNonNullArgCheck(RValue RV, QualType ArgType, NNAttr = getNonNullAttr(AC.getDecl(), PVD, ArgType, ArgNo); bool CanCheckNullability = false; - if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD) { + if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD && + !PVD->getType()->isRecordType()) { auto Nullability = PVD->getType()->getNullability(); CanCheckNullability = Nullability && *Nullability == NullabilityKind::NonNull && diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 1ad905078d349c..c492929f061798 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -974,7 +974,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy, // return value. Initialize the flag to 'true' and refine it in EmitParmDecl. if (SanOpts.has(SanitizerKind::NullabilityReturn)) { auto Nullability = FnRetTy->getNullability(); - if (Nullability && *Nullability == NullabilityKind::NonNull) { + if (Nullability && *Nullability == NullabilityKind::NonNull && + !FnRetTy->isRecordType()) { if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) && CurCodeDecl && CurCodeDecl->getAttr<ReturnsNonNullAttr>())) RetValNullabilityPrecondition = diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 62632b2d79792e..839113f644bc23 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1494,6 +1494,15 @@ void Parser::ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs) { } } +void Parser::ParseNullabilityClassAttributes(ParsedAttributes &attrs) { + while (Tok.is(tok::kw__Nullable)) { + IdentifierInfo *AttrName = Tok.getIdentifierInfo(); + auto Kind = Tok.getKind(); + SourceLocation AttrNameLoc = ConsumeToken(); + attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, Kind); + } +} + /// Determine whether the following tokens are valid after a type-specifier /// which could be a standalone declaration. This will conservatively return /// true if there's any doubt, and is appropriate for insert-';' fixits. @@ -1675,15 +1684,21 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, ParsedAttributes attrs(AttrFactory); // If attributes exist after tag, parse them. - MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs); - - // Parse inheritance specifiers. - if (Tok.isOneOf(tok::kw___single_inheritance, tok::kw___multiple_inheritance, - tok::kw___virtual_inheritance)) - ParseMicrosoftInheritanceClassAttributes(attrs); - - // Allow attributes to precede or succeed the inheritance specifiers. - MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs); + for (;;) { + MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs); + // Parse inheritance specifiers. + if (Tok.isOneOf(tok::kw___single_inheritance, + tok::kw___multiple_inheritance, + tok::kw___virtual_inheritance)) { + ParseMicrosoftInheritanceClassAttributes(attrs); + continue; + } + if (Tok.is(tok::kw__Nullable)) { + ParseNullabilityClassAttributes(attrs); + continue; + } + break; + } // Source location used by FIXIT to insert misplaced // C++11 attributes diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index 0dcf42e4899713..a5dd158808f26b 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -215,6 +215,18 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) { inferGslPointerAttribute(Record, Record); } +void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) { + static llvm::StringSet<> Nullable{ + "auto_ptr", "shared_ptr", "unique_ptr", "exception_ptr", + "coroutine_handle", "function", "move_only_function", + }; + + if (CRD->isInStdNamespace() && Nullable.count(CRD->getName()) && + !CRD->hasAttr<TypeNullableAttr>()) + for (Decl *Redecl : CRD->redecls()) + Redecl->addAttr(TypeNullableAttr::CreateImplicit(Context)); +} + void Sema::ActOnPragmaOptionsAlign(PragmaOptionsAlignKind Kind, SourceLocation PragmaLoc) { PragmaMsStackAction Action = Sema::PSK_Reset; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index e8bfb215a5b4c5..1778f632c12aad 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -27,6 +27,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.h" #include "clang/AST/FormatString.h" +#include "clang/AST/IgnoreExpr.h" #include "clang/AST/NSAPI.h" #include "clang/AST/NonTrivialTypeVisitor.h" #include "clang/AST/OperationKinds.h" @@ -7154,6 +7155,14 @@ bool Sema::getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember, /// /// Returns true if the value evaluates to null. static bool CheckNonNullExpr(Sema &S, const Expr *Expr) { + // Treat (smart) pointers constructed from nullptr as null, whether we can + // const-evaluate them or not. + // This must happen first: the smart pointer expr might have _Nonnull type! + if (isa<CXXNullPtrLiteralExpr>( + IgnoreExprNodes(Expr, IgnoreImplicitAsWrittenSingleStep, + IgnoreElidableImplicitConstructorSingleStep))) + return true; + // If the expression has non-null type, it doesn't evaluate to null. if (auto nullability = Expr->IgnoreImplicit()->getType()->getNullability()) { if (*nullability == NullabilityKind::NonNull) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 10b5c271f25c10..123e210960f517 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -18171,8 +18171,10 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc, if (PrevDecl) mergeDeclAttributes(New, PrevDecl); - if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New)) + if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New)) { inferGslOwnerPointerAttribute(CXXRD); + inferNullableClassAttribute(CXXRD); + } // If there's a #pragma GCC visibility in scope, set the visibility of this // record. diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 8a204b1d3b88d9..aa04179b2b43f4 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5955,6 +5955,20 @@ static void handleBuiltinAliasAttr(Sema &S, Decl *D, D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident)); } +static void handleNullableTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (AL.isUsedAsTypeAttr()) + return; + + if (auto *CRD = dyn_cast<CXXRecordDecl>(D); + !D || !(CRD->isClass() || CRD->isStruct())) { + S.Diag(AL.getRange().getBegin(), diag::err_attribute_wrong_decl_type_str) + << AL << AL.isRegularKeywordAttribute() << "classes"; + return; + } + + handleSimpleAttribute<TypeNullableAttr>(S, D, AL); +} + static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (!AL.hasParsedType()) { S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1; @@ -9862,6 +9876,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_UsingIfExists: handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL); break; + + case ParsedAttr::AT_TypeNullable: + handleNullableTypeAttr(S, D, AL); + break; } } diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 0fd458837163e5..89ed4e19fb8fb7 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -7050,6 +7050,11 @@ PerformConstructorInitialization(Sema &S, hasCopyOrMoveCtorParam(S.Context, getConstructorInfo(Step.Function.FoundDecl)); + // A smart pointer constructed from a nullable pointer is nullable. + if (NumArgs == 1 && !Kind.isExplicitCast()) + S.diagnoseNullableToNonnullConversion( + Entity.getType(), Args.front()->getType(), Kind.getLocation()); + // Determine the arguments required to actually perform the constructor // call. if (S.CompleteConstructorCall(Constructor, Step.Type, Args, Loc, diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index f7645422348b65..03ac17c5be4b04 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -14706,6 +14706,13 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, } } + // Check for nonnull = nullable. + // This won't be caught in the arg's initialization: the parameter to + // the assignment operator is not marked nonnull. + if (Op == OO_Equal) + diagnoseNullableToNonnullConversion(Args[0]->getType(), + Args[1]->getType(), OpLoc); + // Convert the arguments. if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) { // Best->Access is only meaningful for class members. diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 1a975a8d0a0df5..c8d8b78cf57bb4 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -2164,6 +2164,7 @@ DeclResult Sema::CheckClassTemplate( AddPushedVisibilityAttribute(NewClass); inferGslOwnerPointerAttribute(NewClass); + inferNullableClassAttribute(NewClass); if (TUK != TUK_Friend) { // Per C++ [basic.scope.temp]p2, skip the template parameter scopes. diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 1e43e36016a66f..517152397a0a68 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -4705,6 +4705,18 @@ static bool DiagnoseMultipleAddrSpaceAttributes(Sema &S, LangAS ASOld, return false; } +// Whether this is a type broadly expected to have nullability attached. +// These types are affected by `#pragma assume_nonnull`, and missing nullability +// will be diagnosed with -Wnullability-completeness. +static bool shouldHaveNullability(QualType T) { + return T->canHaveNullability(/*ResultIfUnknown=*/false) && + // For now, do not infer/require nullability on C++ smart pointers. + // It's unclear whether the pragma's behavior is useful for C++. + // e.g. treating type-aliases and template-type-parameters differently + // from types of declarations can be surprising. + !isa<RecordType>(T); +} + static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, QualType declSpecType, TypeSourceInfo *TInfo) { @@ -4823,8 +4835,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, // inner pointers. complainAboutMissingNullability = CAMN_InnerPointers; - if (T->canHaveNullability(/*ResultIfUnknown*/ false) && - !T->getNullability()) { + if (shouldHaveNullability(T) && !T->getNullability()) { // Note that we allow but don't require nullability on dependent types. ++NumPointersRemaining; } @@ -5047,8 +5058,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, // If the type itself could have nullability but does not, infer pointer // nullability and perform consistency checking. if (S.CodeSynthesisContexts.empty()) { - if (T->canHaveNullability(/*ResultIfUnknown*/ false) && - !T->getNullability()) { + if (shouldHaveNullability(T) && !T->getNullability()) { if (isVaList(T)) { // Record that we've seen a pointer, but do nothing else. if (NumPointersRemaining > 0) diff --git a/clang/test/SemaCXX/nullability.cpp b/clang/test/SemaCXX/nullability.cpp index 8d0c4dc195a6bd..5d2df9d1d2e8eb 100644 --- a/clang/test/SemaCXX/nullability.cpp +++ b/clang/test/SemaCXX/nullability.cpp @@ -4,6 +4,10 @@ #else # error nullability feature should be defined #endif +#if __has_feature(nullability_on_classes) +#else +# error smart-pointer feature should be defined +#endif #include "nullability-completeness.h" @@ -27,6 +31,7 @@ template<typename T> struct AddNonNull { typedef _Nonnull T type; // expected-error{{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'int'}} // expected-error@-1{{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'std::nullptr_t'}} + // expected-error@-2{{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'NotPtr'}} }; typedef AddNonNull<int *>::type nonnull_int_ptr_1; @@ -35,6 +40,33 @@ typedef AddNonNull<nullptr_t>::type nonnull_int_ptr_3; // expected-note{{in inst typedef AddNonNull<int>::type nonnull_non_pointer_1; // expected-note{{in instantiation of template class 'AddNonNull<int>' requested here}} +// Nullability on C++ class types (smart pointers). +struct NotPtr{}; +typedef AddNonNull<NotPtr>::type nonnull_non_pointer_2; // expected-note{{in instantiation}} +struct _Nullable SmartPtr{ + SmartPtr(); + SmartPtr(nullptr_t); + SmartPtr(const SmartPtr&); + SmartPtr(SmartPtr&&); + SmartPtr &operator=(const SmartPtr&); + SmartPtr &operator=(SmartPtr&&); +}; +typedef AddNonNull<SmartPtr>::type nonnull_smart_pointer_1; +template<class> struct _Nullable SmartPtrTemplate{}; +typedef AddNonNull<SmartPtrTemplate<int>>::type nonnull_smart_pointer_2; +namespace std { inline namespace __1 { + template <class> class unique_ptr {}; + template <class> class function; + template <class Ret, class... Args> class function<Ret(Args...)> {}; +} } +typedef AddNonNull<std::unique_ptr<int>>::type nonnull_smart_pointer_3; +typedef AddNonNull<std::function<int()>>::type nonnull_smart_pointer_4; + +class Derived : public SmartPtr {}; +Derived _Nullable x; // expected-error {{'_Nullable' cannot be applied}} +class DerivedPrivate : private SmartPtr {}; +DerivedPrivate _Nullable y; // expected-error {{'_Nullable' cannot be applied}} + // Non-null checking within a template. template<typename T> struct AddNonNull2 { @@ -54,6 +86,7 @@ void (*& accepts_nonnull_2)(_Nonnull int *ptr) = accepts_nonnull_1; void (X::* accepts_nonnull_3)(_Nonnull int *ptr); void accepts_nonnull_4(_Nonnull int *ptr); void (&accepts_nonnull_5)(_Nonnull int *ptr) = accepts_nonnull_4; +void accepts_nonnull_6(SmartPtr _Nonnull); void test_accepts_nonnull_null_pointer_literal(X *x) { accepts_nonnull_1(0); // expected-warning{{null passed to a callee that requires a non-null argument}} @@ -61,6 +94,8 @@ void test_accepts_nonnull_null_pointer_literal(X *x) { (x->*accepts_nonnull_3)(0); // expected-warning{{null passed to a callee that requires a non-null argument}} accepts_nonnull_4(0); // expected-warning{{null passed to a callee that requires a non-null argument}} accepts_nonnull_5(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + + accepts_nonnull_6(nullptr); // expected-warning{{null passed to a callee that requires a non-null argument}} } template<void FP(_Nonnull int*)> @@ -71,6 +106,7 @@ void test_accepts_nonnull_null_pointer_literal_template() { template void test_accepts_nonnull_null_pointer_literal_template<&accepts_nonnull_4>(); // expected-note{{instantiation of function template specialization}} void TakeNonnull(void *_Nonnull); +void TakeSmartNonnull(SmartPtr _Nonnull); // Check different forms of assignment to a nonull type from a nullable one. void AssignAndInitNonNull() { void *_Nullable nullable; @@ -81,12 +117,26 @@ void AssignAndInitNonNull() { void *_Nonnull nonnull; nonnull = nullable; // expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull'}} nonnull = {nullable}; // expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull'}} - TakeNonnull(nullable); //expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull}} TakeNonnull(nonnull); // OK + nonnull = (void *_Nonnull)nullable; // explicit cast OK + + SmartPtr _Nullable s_nullable; + SmartPtr _Nonnull s(s_nullable); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s2{s_nullable}; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s3 = {s_nullable}; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s4 = s_nullable; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s_nonnull; + s_nonnull = s_nullable; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + s_nonnull = {s_nullable}; // no warning here - might be nice? + TakeSmartNonnull(s_nullable); //expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull}} + TakeSmartNonnull(s_nonnull); // OK + s_nonnull = (SmartPtr _Nonnull)s_nullable; // explicit cast OK + s_nonnull = static_cast<SmartPtr _Nonnull>(s_nullable); // explicit cast OK } void *_Nullable ReturnNullable(); +SmartPtr _Nullable ReturnSmartNullable(); void AssignAndInitNonNullFromFn() { void *_Nonnull p(ReturnNullable()); // expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull'}} @@ -96,8 +146,16 @@ void AssignAndInitNonNullFromFn() { void *_Nonnull nonnull; nonnull = ReturnNullable(); // expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull'}} nonnull = {ReturnNullable()}; // expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull'}} - TakeNonnull(ReturnNullable()); //expected-warning{{implicit conversion from nullable pointer 'void * _Nullable' to non-nullable pointer type 'void * _Nonnull}} + + SmartPtr _Nonnull s(ReturnSmartNullable()); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s2{ReturnSmartNullable()}; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s3 = {ReturnSmartNullable()}; // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s4 = ReturnSmartNullable(); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + SmartPtr _Nonnull s_nonnull; + s_nonnull = ReturnSmartNullable(); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} + s_nonnull = {ReturnSmartNullable()}; + TakeSmartNonnull(ReturnSmartNullable()); //expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull}} } void ConditionalExpr(bool c) { >From 3eaacb6a1a4c64befdf812f45f69f89938113b9e Mon Sep 17 00:00:00 2001 From: Sam McCall <sam.mcc...@gmail.com> Date: Mon, 11 Mar 2024 14:58:23 +0100 Subject: [PATCH 2/2] Address review comments --- clang/include/clang/Basic/AttrDocs.td | 9 +++++---- clang/lib/Sema/SemaDeclAttr.cpp | 2 +- clang/test/Sema/nullability.c | 2 ++ clang/test/SemaCXX/nullability.cpp | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 8c248fb052f19e..f5acbd7613f604 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4097,10 +4097,11 @@ non-underscored keywords. For example: @property (readonly, nonnull) NSArray *subviews; @end -As well as built-in pointer types, ithe nullability attributes can be attached -to nullable types from the C++ standard library such as ``std::unique_ptr`` and -``std::function``, as well as C++ classes marked with the ``_Nullable`` -attribute. +As well as built-in pointer types, the nullability attributes can be attached +to C++ classes marked with the ``_Nullable`` attribute, as well as the C++ +standard library types ``unique_ptr``, ``shared_ptr``, ``auto_ptr``, +``exception_ptr``, ``function``, ``move_only_function`` and +``coroutine_handle``. }]; } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index aa04179b2b43f4..b54577538d7e18 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5960,7 +5960,7 @@ static void handleNullableTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { return; if (auto *CRD = dyn_cast<CXXRecordDecl>(D); - !D || !(CRD->isClass() || CRD->isStruct())) { + !CRD || !(CRD->isClass() || CRD->isStruct())) { S.Diag(AL.getRange().getBegin(), diag::err_attribute_wrong_decl_type_str) << AL << AL.isRegularKeywordAttribute() << "classes"; return; diff --git a/clang/test/Sema/nullability.c b/clang/test/Sema/nullability.c index 7d193bea46771f..0401516233b6db 100644 --- a/clang/test/Sema/nullability.c +++ b/clang/test/Sema/nullability.c @@ -248,3 +248,5 @@ void arraysInBlocks(void) { void (^withTypedefBad)(INTS _Nonnull [2]) = // expected-error {{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'INTS' (aka 'int[4]')}} ^(INTS _Nonnull x[2]) {}; // expected-error {{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'INTS' (aka 'int[4]')}} } + +struct _Nullable NotCplusplusClass {}; // expected-error {{'_Nullable' attribute only applies to classes}} diff --git a/clang/test/SemaCXX/nullability.cpp b/clang/test/SemaCXX/nullability.cpp index 5d2df9d1d2e8eb..d52ba4efaccdbd 100644 --- a/clang/test/SemaCXX/nullability.cpp +++ b/clang/test/SemaCXX/nullability.cpp @@ -155,7 +155,7 @@ void AssignAndInitNonNullFromFn() { SmartPtr _Nonnull s_nonnull; s_nonnull = ReturnSmartNullable(); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} s_nonnull = {ReturnSmartNullable()}; - TakeSmartNonnull(ReturnSmartNullable()); //expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull}} + TakeSmartNonnull(ReturnSmartNullable()); // expected-warning{{implicit conversion from nullable pointer 'SmartPtr _Nullable' to non-nullable pointer type 'SmartPtr _Nonnull'}} } void ConditionalExpr(bool c) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits