Skip to content

Commit 7ef602b

Browse files
authored
Reapply "[clang][nullability] allow _Nonnull etc on nullable class types (#82705)" (#87325)
This reverts commit 28760b6. The last commit was missing the new testcase, now fixed.
1 parent 0b13e2c commit 7ef602b

22 files changed

+233
-29
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,21 @@ Attribute Changes in Clang
253253
added a new extension query ``__has_extension(swiftcc)`` corresponding to the
254254
``__attribute__((swiftcc))`` attribute.
255255

256+
- The ``_Nullable`` and ``_Nonnull`` family of type attributes can now apply
257+
to certain C++ class types, such as smart pointers:
258+
``void useObject(std::unique_ptr<Object> _Nonnull obj);``.
259+
260+
This works for standard library types including ``unique_ptr``, ``shared_ptr``,
261+
and ``function``. See
262+
`the attribute reference documentation <https://llvm.org/docs/AttributeReference.html#nullability-attributes>`_
263+
for the full list.
264+
265+
- The ``_Nullable`` attribute can be applied to C++ class declarations:
266+
``template <class T> class _Nullable MySmartPointer {};``.
267+
268+
This allows the ``_Nullable`` and ``_Nonnull`` family of type attributes to
269+
apply to this class.
270+
256271
Improvements to Clang's diagnostics
257272
-----------------------------------
258273
- Clang now applies syntax highlighting to the code snippets it

clang/include/clang/Basic/Attr.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2178,9 +2178,10 @@ def TypeNonNull : TypeAttr {
21782178
let Documentation = [TypeNonNullDocs];
21792179
}
21802180

2181-
def TypeNullable : TypeAttr {
2181+
def TypeNullable : DeclOrTypeAttr {
21822182
let Spellings = [CustomKeyword<"_Nullable">];
21832183
let Documentation = [TypeNullableDocs];
2184+
// let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
21842185
}
21852186

21862187
def TypeNullableResult : TypeAttr {

clang/include/clang/Basic/AttrDocs.td

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4151,6 +4151,20 @@ non-underscored keywords. For example:
41514151
@property (assign, nullable) NSView *superview;
41524152
@property (readonly, nonnull) NSArray *subviews;
41534153
@end
4154+
4155+
As well as built-in pointer types, the nullability attributes can be attached
4156+
to C++ classes marked with the ``_Nullable`` attribute.
4157+
4158+
The following C++ standard library types are considered nullable:
4159+
``unique_ptr``, ``shared_ptr``, ``auto_ptr``, ``exception_ptr``, ``function``,
4160+
``move_only_function`` and ``coroutine_handle``.
4161+
4162+
Types should be marked nullable only where the type itself leaves nullability
4163+
ambiguous. For example, ``std::optional`` is not marked ``_Nullable``, because
4164+
``optional<int> _Nullable`` is redundant and ``optional<int> _Nonnull`` is
4165+
not a useful type. ``std::weak_ptr`` is not nullable, because its nullability
4166+
can change with no visible modification, so static annotation is unlikely to be
4167+
unhelpful.
41544168
}];
41554169
}
41564170

@@ -4185,6 +4199,17 @@ The ``_Nullable`` nullability qualifier indicates that a value of the
41854199
int fetch_or_zero(int * _Nullable ptr);
41864200

41874201
a caller of ``fetch_or_zero`` can provide null.
4202+
4203+
The ``_Nullable`` attribute on classes indicates that the given class can
4204+
represent null values, and so the ``_Nullable``, ``_Nonnull`` etc qualifiers
4205+
make sense for this type. For example:
4206+
4207+
.. code-block:: c
4208+
4209+
class _Nullable ArenaPointer { ... };
4210+
4211+
ArenaPointer _Nonnull x = ...;
4212+
ArenaPointer _Nullable y = nullptr;
41884213
}];
41894214
}
41904215

clang/include/clang/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ EXTENSION(define_target_os_macros,
9494
FEATURE(enumerator_attributes, true)
9595
FEATURE(nullability, true)
9696
FEATURE(nullability_on_arrays, true)
97+
FEATURE(nullability_on_classes, true)
9798
FEATURE(nullability_nullable_result, true)
9899
FEATURE(memory_sanitizer,
99100
LangOpts.Sanitize.hasOneOf(SanitizerKind::Memory |

clang/include/clang/Parse/Parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,6 +3014,7 @@ class Parser : public CodeCompletionHandler {
30143014
void DiagnoseAndSkipExtendedMicrosoftTypeAttributes();
30153015
SourceLocation SkipExtendedMicrosoftTypeAttributes();
30163016
void ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs);
3017+
void ParseNullabilityClassAttributes(ParsedAttributes &attrs);
30173018
void ParseBorlandTypeAttributes(ParsedAttributes &attrs);
30183019
void ParseOpenCLKernelAttributes(ParsedAttributes &attrs);
30193020
void ParseOpenCLQualifiers(ParsedAttributes &Attrs);

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,9 @@ class Sema final {
16601660
/// Add [[gsl::Pointer]] attributes for std:: types.
16611661
void inferGslPointerAttribute(TypedefNameDecl *TD);
16621662

1663+
/// Add _Nullable attributes for std:: types.
1664+
void inferNullableClassAttribute(CXXRecordDecl *CRD);
1665+
16631666
enum PragmaOptionsAlignKind {
16641667
POAK_Native, // #pragma options align=native
16651668
POAK_Natural, // #pragma options align=natural

clang/lib/AST/Type.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4652,16 +4652,15 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
46524652
case Type::Auto:
46534653
return ResultIfUnknown;
46544654

4655-
// Dependent template specializations can instantiate to pointer
4656-
// types unless they're known to be specializations of a class
4657-
// template.
4655+
// Dependent template specializations could instantiate to pointer types.
46584656
case Type::TemplateSpecialization:
4659-
if (TemplateDecl *templateDecl
4660-
= cast<TemplateSpecializationType>(type.getTypePtr())
4661-
->getTemplateName().getAsTemplateDecl()) {
4662-
if (isa<ClassTemplateDecl>(templateDecl))
4663-
return false;
4664-
}
4657+
// If it's a known class template, we can already check if it's nullable.
4658+
if (TemplateDecl *templateDecl =
4659+
cast<TemplateSpecializationType>(type.getTypePtr())
4660+
->getTemplateName()
4661+
.getAsTemplateDecl())
4662+
if (auto *CTD = dyn_cast<ClassTemplateDecl>(templateDecl))
4663+
return CTD->getTemplatedDecl()->hasAttr<TypeNullableAttr>();
46654664
return ResultIfUnknown;
46664665

46674666
case Type::Builtin:
@@ -4718,6 +4717,17 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
47184717
}
47194718
llvm_unreachable("unknown builtin type");
47204719

4720+
case Type::Record: {
4721+
const RecordDecl *RD = cast<RecordType>(type)->getDecl();
4722+
// For template specializations, look only at primary template attributes.
4723+
// This is a consistent regardless of whether the instantiation is known.
4724+
if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
4725+
return CTSD->getSpecializedTemplate()
4726+
->getTemplatedDecl()
4727+
->hasAttr<TypeNullableAttr>();
4728+
return RD->hasAttr<TypeNullableAttr>();
4729+
}
4730+
47214731
// Non-pointer types.
47224732
case Type::Complex:
47234733
case Type::LValueReference:
@@ -4735,7 +4745,6 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
47354745
case Type::DependentAddressSpace:
47364746
case Type::FunctionProto:
47374747
case Type::FunctionNoProto:
4738-
case Type::Record:
47394748
case Type::DeducedTemplateSpecialization:
47404749
case Type::Enum:
47414750
case Type::InjectedClassName:

clang/lib/CodeGen/CGCall.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4379,7 +4379,8 @@ void CodeGenFunction::EmitNonNullArgCheck(RValue RV, QualType ArgType,
43794379
NNAttr = getNonNullAttr(AC.getDecl(), PVD, ArgType, ArgNo);
43804380

43814381
bool CanCheckNullability = false;
4382-
if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD) {
4382+
if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD &&
4383+
!PVD->getType()->isRecordType()) {
43834384
auto Nullability = PVD->getType()->getNullability();
43844385
CanCheckNullability = Nullability &&
43854386
*Nullability == NullabilityKind::NonNull &&

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
990990
// return value. Initialize the flag to 'true' and refine it in EmitParmDecl.
991991
if (SanOpts.has(SanitizerKind::NullabilityReturn)) {
992992
auto Nullability = FnRetTy->getNullability();
993-
if (Nullability && *Nullability == NullabilityKind::NonNull) {
993+
if (Nullability && *Nullability == NullabilityKind::NonNull &&
994+
!FnRetTy->isRecordType()) {
994995
if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) &&
995996
CurCodeDecl && CurCodeDecl->getAttr<ReturnsNonNullAttr>()))
996997
RetValNullabilityPrecondition =

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,15 @@ void Parser::ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs) {
15021502
}
15031503
}
15041504

1505+
void Parser::ParseNullabilityClassAttributes(ParsedAttributes &attrs) {
1506+
while (Tok.is(tok::kw__Nullable)) {
1507+
IdentifierInfo *AttrName = Tok.getIdentifierInfo();
1508+
auto Kind = Tok.getKind();
1509+
SourceLocation AttrNameLoc = ConsumeToken();
1510+
attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, Kind);
1511+
}
1512+
}
1513+
15051514
/// Determine whether the following tokens are valid after a type-specifier
15061515
/// which could be a standalone declaration. This will conservatively return
15071516
/// true if there's any doubt, and is appropriate for insert-';' fixits.
@@ -1683,15 +1692,21 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
16831692

16841693
ParsedAttributes attrs(AttrFactory);
16851694
// If attributes exist after tag, parse them.
1686-
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1687-
1688-
// Parse inheritance specifiers.
1689-
if (Tok.isOneOf(tok::kw___single_inheritance, tok::kw___multiple_inheritance,
1690-
tok::kw___virtual_inheritance))
1691-
ParseMicrosoftInheritanceClassAttributes(attrs);
1692-
1693-
// Allow attributes to precede or succeed the inheritance specifiers.
1694-
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1695+
for (;;) {
1696+
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1697+
// Parse inheritance specifiers.
1698+
if (Tok.isOneOf(tok::kw___single_inheritance,
1699+
tok::kw___multiple_inheritance,
1700+
tok::kw___virtual_inheritance)) {
1701+
ParseMicrosoftInheritanceClassAttributes(attrs);
1702+
continue;
1703+
}
1704+
if (Tok.is(tok::kw__Nullable)) {
1705+
ParseNullabilityClassAttributes(attrs);
1706+
continue;
1707+
}
1708+
break;
1709+
}
16951710

16961711
// Source location used by FIXIT to insert misplaced
16971712
// C++11 attributes

clang/lib/Sema/SemaAttr.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
215215
inferGslPointerAttribute(Record, Record);
216216
}
217217

218+
void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) {
219+
static llvm::StringSet<> Nullable{
220+
"auto_ptr", "shared_ptr", "unique_ptr", "exception_ptr",
221+
"coroutine_handle", "function", "move_only_function",
222+
};
223+
224+
if (CRD->isInStdNamespace() && Nullable.count(CRD->getName()) &&
225+
!CRD->hasAttr<TypeNullableAttr>())
226+
for (Decl *Redecl : CRD->redecls())
227+
Redecl->addAttr(TypeNullableAttr::CreateImplicit(Context));
228+
}
229+
218230
void Sema::ActOnPragmaOptionsAlign(PragmaOptionsAlignKind Kind,
219231
SourceLocation PragmaLoc) {
220232
PragmaMsStackAction Action = Sema::PSK_Reset;

clang/lib/Sema/SemaChecking.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "clang/AST/ExprObjC.h"
2828
#include "clang/AST/ExprOpenMP.h"
2929
#include "clang/AST/FormatString.h"
30+
#include "clang/AST/IgnoreExpr.h"
3031
#include "clang/AST/NSAPI.h"
3132
#include "clang/AST/NonTrivialTypeVisitor.h"
3233
#include "clang/AST/OperationKinds.h"
@@ -7610,6 +7611,14 @@ bool Sema::getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember,
76107611
///
76117612
/// Returns true if the value evaluates to null.
76127613
static bool CheckNonNullExpr(Sema &S, const Expr *Expr) {
7614+
// Treat (smart) pointers constructed from nullptr as null, whether we can
7615+
// const-evaluate them or not.
7616+
// This must happen first: the smart pointer expr might have _Nonnull type!
7617+
if (isa<CXXNullPtrLiteralExpr>(
7618+
IgnoreExprNodes(Expr, IgnoreImplicitAsWrittenSingleStep,
7619+
IgnoreElidableImplicitConstructorSingleStep)))
7620+
return true;
7621+
76137622
// If the expression has non-null type, it doesn't evaluate to null.
76147623
if (auto nullability = Expr->IgnoreImplicit()->getType()->getNullability()) {
76157624
if (*nullability == NullabilityKind::NonNull)

clang/lib/Sema/SemaDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18319,8 +18319,10 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1831918319
if (PrevDecl)
1832018320
mergeDeclAttributes(New, PrevDecl);
1832118321

18322-
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New))
18322+
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New)) {
1832318323
inferGslOwnerPointerAttribute(CXXRD);
18324+
inferNullableClassAttribute(CXXRD);
18325+
}
1832418326

1832518327
// If there's a #pragma GCC visibility in scope, set the visibility of this
1832618328
// record.

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5982,6 +5982,20 @@ static void handleBuiltinAliasAttr(Sema &S, Decl *D,
59825982
D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident));
59835983
}
59845984

5985+
static void handleNullableTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
5986+
if (AL.isUsedAsTypeAttr())
5987+
return;
5988+
5989+
if (auto *CRD = dyn_cast<CXXRecordDecl>(D);
5990+
!CRD || !(CRD->isClass() || CRD->isStruct())) {
5991+
S.Diag(AL.getRange().getBegin(), diag::err_attribute_wrong_decl_type_str)
5992+
<< AL << AL.isRegularKeywordAttribute() << "classes";
5993+
return;
5994+
}
5995+
5996+
handleSimpleAttribute<TypeNullableAttr>(S, D, AL);
5997+
}
5998+
59855999
static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
59866000
if (!AL.hasParsedType()) {
59876001
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
@@ -9933,6 +9947,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
99339947
case ParsedAttr::AT_UsingIfExists:
99349948
handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
99359949
break;
9950+
9951+
case ParsedAttr::AT_TypeNullable:
9952+
handleNullableTypeAttr(S, D, AL);
9953+
break;
99369954
}
99379955
}
99389956

clang/lib/Sema/SemaInit.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7082,6 +7082,11 @@ PerformConstructorInitialization(Sema &S,
70827082
hasCopyOrMoveCtorParam(S.Context,
70837083
getConstructorInfo(Step.Function.FoundDecl));
70847084

7085+
// A smart pointer constructed from a nullable pointer is nullable.
7086+
if (NumArgs == 1 && !Kind.isExplicitCast())
7087+
S.diagnoseNullableToNonnullConversion(
7088+
Entity.getType(), Args.front()->getType(), Kind.getLocation());
7089+
70857090
// Determine the arguments required to actually perform the constructor
70867091
// call.
70877092
if (S.CompleteConstructorCall(Constructor, Step.Type, Args, Loc,

clang/lib/Sema/SemaOverload.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14826,6 +14826,13 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1482614826
}
1482714827
}
1482814828

14829+
// Check for nonnull = nullable.
14830+
// This won't be caught in the arg's initialization: the parameter to
14831+
// the assignment operator is not marked nonnull.
14832+
if (Op == OO_Equal)
14833+
diagnoseNullableToNonnullConversion(Args[0]->getType(),
14834+
Args[1]->getType(), OpLoc);
14835+
1482914836
// Convert the arguments.
1483014837
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
1483114838
// Best->Access is only meaningful for class members.

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,7 @@ DeclResult Sema::CheckClassTemplate(
21912191

21922192
AddPushedVisibilityAttribute(NewClass);
21932193
inferGslOwnerPointerAttribute(NewClass);
2194+
inferNullableClassAttribute(NewClass);
21942195

21952196
if (TUK != TUK_Friend) {
21962197
// Per C++ [basic.scope.temp]p2, skip the template parameter scopes.

clang/lib/Sema/SemaType.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4717,6 +4717,18 @@ static bool DiagnoseMultipleAddrSpaceAttributes(Sema &S, LangAS ASOld,
47174717
return false;
47184718
}
47194719

4720+
// Whether this is a type broadly expected to have nullability attached.
4721+
// These types are affected by `#pragma assume_nonnull`, and missing nullability
4722+
// will be diagnosed with -Wnullability-completeness.
4723+
static bool shouldHaveNullability(QualType T) {
4724+
return T->canHaveNullability(/*ResultIfUnknown=*/false) &&
4725+
// For now, do not infer/require nullability on C++ smart pointers.
4726+
// It's unclear whether the pragma's behavior is useful for C++.
4727+
// e.g. treating type-aliases and template-type-parameters differently
4728+
// from types of declarations can be surprising.
4729+
!isa<RecordType>(T->getCanonicalTypeInternal());
4730+
}
4731+
47204732
static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
47214733
QualType declSpecType,
47224734
TypeSourceInfo *TInfo) {
@@ -4835,8 +4847,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
48354847
// inner pointers.
48364848
complainAboutMissingNullability = CAMN_InnerPointers;
48374849

4838-
if (T->canHaveNullability(/*ResultIfUnknown*/ false) &&
4839-
!T->getNullability()) {
4850+
if (shouldHaveNullability(T) && !T->getNullability()) {
48404851
// Note that we allow but don't require nullability on dependent types.
48414852
++NumPointersRemaining;
48424853
}
@@ -5059,8 +5070,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
50595070
// If the type itself could have nullability but does not, infer pointer
50605071
// nullability and perform consistency checking.
50615072
if (S.CodeSynthesisContexts.empty()) {
5062-
if (T->canHaveNullability(/*ResultIfUnknown*/ false) &&
5063-
!T->getNullability()) {
5073+
if (shouldHaveNullability(T) && !T->getNullability()) {
50645074
if (isVaList(T)) {
50655075
// Record that we've seen a pointer, but do nothing else.
50665076
if (NumPointersRemaining > 0)

clang/test/Sema/nullability.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,5 @@ void arraysInBlocks(void) {
248248
void (^withTypedefBad)(INTS _Nonnull [2]) = // expected-error {{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'INTS' (aka 'int[4]')}}
249249
^(INTS _Nonnull x[2]) {}; // expected-error {{nullability specifier '_Nonnull' cannot be applied to non-pointer type 'INTS' (aka 'int[4]')}}
250250
}
251+
252+
struct _Nullable NotCplusplusClass {}; // expected-error {{'_Nullable' attribute only applies to classes}}

0 commit comments

Comments
 (0)