Skip to content

Commit bbbcc1d

Browse files
committed
Reapply "[clang][nullability] allow _Nonnull etc on nullable class types (#82705)"
This reverts commit ca4c4a6. This was intended not to introduce new consistency diagnostics for smart pointer types, but failed to ignore sugar around types when detecting this. Fixed and test added.
1 parent eee8c61 commit bbbcc1d

21 files changed

+226
-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
@@ -1655,6 +1655,9 @@ class Sema final {
16551655
/// Add [[gsl::Pointer]] attributes for std:: types.
16561656
void inferGslPointerAttribute(TypedefNameDecl *TD);
16571657

1658+
/// Add _Nullable attributes for std:: types.
1659+
void inferNullableClassAttribute(CXXRecordDecl *CRD);
1660+
16581661
enum PragmaOptionsAlignKind {
16591662
POAK_Native, // #pragma options align=native
16601663
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
@@ -4642,16 +4642,15 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
46424642
case Type::Auto:
46434643
return ResultIfUnknown;
46444644

4645-
// Dependent template specializations can instantiate to pointer
4646-
// types unless they're known to be specializations of a class
4647-
// template.
4645+
// Dependent template specializations could instantiate to pointer types.
46484646
case Type::TemplateSpecialization:
4649-
if (TemplateDecl *templateDecl
4650-
= cast<TemplateSpecializationType>(type.getTypePtr())
4651-
->getTemplateName().getAsTemplateDecl()) {
4652-
if (isa<ClassTemplateDecl>(templateDecl))
4653-
return false;
4654-
}
4647+
// If it's a known class template, we can already check if it's nullable.
4648+
if (TemplateDecl *templateDecl =
4649+
cast<TemplateSpecializationType>(type.getTypePtr())
4650+
->getTemplateName()
4651+
.getAsTemplateDecl())
4652+
if (auto *CTD = dyn_cast<ClassTemplateDecl>(templateDecl))
4653+
return CTD->getTemplatedDecl()->hasAttr<TypeNullableAttr>();
46554654
return ResultIfUnknown;
46564655

46574656
case Type::Builtin:
@@ -4708,6 +4707,17 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
47084707
}
47094708
llvm_unreachable("unknown builtin type");
47104709

4710+
case Type::Record: {
4711+
const RecordDecl *RD = cast<RecordType>(type)->getDecl();
4712+
// For template specializations, look only at primary template attributes.
4713+
// This is a consistent regardless of whether the instantiation is known.
4714+
if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
4715+
return CTSD->getSpecializedTemplate()
4716+
->getTemplatedDecl()
4717+
->hasAttr<TypeNullableAttr>();
4718+
return RD->hasAttr<TypeNullableAttr>();
4719+
}
4720+
47114721
// Non-pointer types.
47124722
case Type::Complex:
47134723
case Type::LValueReference:
@@ -4725,7 +4735,6 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
47254735
case Type::DependentAddressSpace:
47264736
case Type::FunctionProto:
47274737
case Type::FunctionNoProto:
4728-
case Type::Record:
47294738
case Type::DeducedTemplateSpecialization:
47304739
case Type::Enum:
47314740
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
@@ -989,7 +989,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
989989
// return value. Initialize the flag to 'true' and refine it in EmitParmDecl.
990990
if (SanOpts.has(SanitizerKind::NullabilityReturn)) {
991991
auto Nullability = FnRetTy->getNullability();
992-
if (Nullability && *Nullability == NullabilityKind::NonNull) {
992+
if (Nullability && *Nullability == NullabilityKind::NonNull &&
993+
!FnRetTy->isRecordType()) {
993994
if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) &&
994995
CurCodeDecl && CurCodeDecl->getAttr<ReturnsNonNullAttr>()))
995996
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"
@@ -7609,6 +7610,14 @@ bool Sema::getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember,
76097610
///
76107611
/// Returns true if the value evaluates to null.
76117612
static bool CheckNonNullExpr(Sema &S, const Expr *Expr) {
7613+
// Treat (smart) pointers constructed from nullptr as null, whether we can
7614+
// const-evaluate them or not.
7615+
// This must happen first: the smart pointer expr might have _Nonnull type!
7616+
if (isa<CXXNullPtrLiteralExpr>(
7617+
IgnoreExprNodes(Expr, IgnoreImplicitAsWrittenSingleStep,
7618+
IgnoreElidableImplicitConstructorSingleStep)))
7619+
return true;
7620+
76127621
// If the expression has non-null type, it doesn't evaluate to null.
76137622
if (auto nullability = Expr->IgnoreImplicit()->getType()->getNullability()) {
76147623
if (*nullability == NullabilityKind::NonNull)

clang/lib/Sema/SemaDecl.cpp

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

18320-
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New))
18320+
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New)) {
1832118321
inferGslOwnerPointerAttribute(CXXRD);
18322+
inferNullableClassAttribute(CXXRD);
18323+
}
1832218324

1832318325
// If there's a #pragma GCC visibility in scope, set the visibility of this
1832418326
// 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
@@ -7079,6 +7079,11 @@ PerformConstructorInitialization(Sema &S,
70797079
hasCopyOrMoveCtorParam(S.Context,
70807080
getConstructorInfo(Step.Function.FoundDecl));
70817081

7082+
// A smart pointer constructed from a nullable pointer is nullable.
7083+
if (NumArgs == 1 && !Kind.isExplicitCast())
7084+
S.diagnoseNullableToNonnullConversion(
7085+
Entity.getType(), Args.front()->getType(), Kind.getLocation());
7086+
70827087
// Determine the arguments required to actually perform the constructor
70837088
// call.
70847089
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
@@ -14811,6 +14811,13 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1481114811
}
1481214812
}
1481314813

14814+
// Check for nonnull = nullable.
14815+
// This won't be caught in the arg's initialization: the parameter to
14816+
// the assignment operator is not marked nonnull.
14817+
if (Op == OO_Equal)
14818+
diagnoseNullableToNonnullConversion(Args[0]->getType(),
14819+
Args[1]->getType(), OpLoc);
14820+
1481414821
// Convert the arguments.
1481514822
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
1481614823
// 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
@@ -2171,6 +2171,7 @@ DeclResult Sema::CheckClassTemplate(
21712171

21722172
AddPushedVisibilityAttribute(NewClass);
21732173
inferGslOwnerPointerAttribute(NewClass);
2174+
inferNullableClassAttribute(NewClass);
21742175

21752176
if (TUK != TUK_Friend) {
21762177
// 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)