Skip to content

Commit 5df9923

Browse files
committed
[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.
1 parent 770fd38 commit 5df9923

File tree

18 files changed

+199
-29
lines changed

18 files changed

+199
-29
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2156,9 +2156,10 @@ def TypeNonNull : TypeAttr {
21562156
let Documentation = [TypeNonNullDocs];
21572157
}
21582158

2159-
def TypeNullable : TypeAttr {
2159+
def TypeNullable : DeclOrTypeAttr {
21602160
let Spellings = [CustomKeyword<"_Nullable">];
21612161
let Documentation = [TypeNullableDocs];
2162+
// let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
21622163
}
21632164

21642165
def TypeNullableResult : TypeAttr {

clang/include/clang/Basic/AttrDocs.td

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,6 +4096,11 @@ non-underscored keywords. For example:
40964096
@property (assign, nullable) NSView *superview;
40974097
@property (readonly, nonnull) NSArray *subviews;
40984098
@end
4099+
4100+
As well as built-in pointer types, ithe nullability attributes can be attached
4101+
to nullable types from the C++ standard library such as ``std::unique_ptr`` and
4102+
``std::function``, as well as C++ classes marked with the ``_Nullable``
4103+
attribute.
40994104
}];
41004105
}
41014106

@@ -4130,6 +4135,17 @@ The ``_Nullable`` nullability qualifier indicates that a value of the
41304135
int fetch_or_zero(int * _Nullable ptr);
41314136

41324137
a caller of ``fetch_or_zero`` can provide null.
4138+
4139+
The ``_Nullable`` attribute on classes indicates that the given class can
4140+
represent null values, and so the ``_Nullable``, ``_Nonnull`` etc qualifiers
4141+
make sense for this type. For example:
4142+
4143+
.. code-block:: c
4144+
4145+
class _Nullable ArenaPointer { ... };
4146+
4147+
ArenaPointer _Nonnull x = ...;
4148+
ArenaPointer _Nullable y = nullptr;
41334149
}];
41344150
}
41354151

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
@@ -3007,6 +3007,7 @@ class Parser : public CodeCompletionHandler {
30073007
void DiagnoseAndSkipExtendedMicrosoftTypeAttributes();
30083008
SourceLocation SkipExtendedMicrosoftTypeAttributes();
30093009
void ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs);
3010+
void ParseNullabilityClassAttributes(ParsedAttributes &attrs);
30103011
void ParseBorlandTypeAttributes(ParsedAttributes &attrs);
30113012
void ParseOpenCLKernelAttributes(ParsedAttributes &attrs);
30123013
void ParseOpenCLQualifiers(ParsedAttributes &Attrs);

clang/include/clang/Sema/Sema.h

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

8007+
/// Add _Nullable attributes for std:: types.
8008+
void inferNullableClassAttribute(CXXRecordDecl *CRD);
8009+
80078010
void CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record);
80088011

80098012
/// Check that the C++ class annoated with "trivial_abi" satisfies all the

clang/lib/AST/Type.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4556,16 +4556,15 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
45564556
case Type::Auto:
45574557
return ResultIfUnknown;
45584558

4559-
// Dependent template specializations can instantiate to pointer
4560-
// types unless they're known to be specializations of a class
4561-
// template.
4559+
// Dependent template specializations could instantiate to pointer types.
45624560
case Type::TemplateSpecialization:
4563-
if (TemplateDecl *templateDecl
4564-
= cast<TemplateSpecializationType>(type.getTypePtr())
4565-
->getTemplateName().getAsTemplateDecl()) {
4566-
if (isa<ClassTemplateDecl>(templateDecl))
4567-
return false;
4568-
}
4561+
// If it's a known class template, we can already check if it's nullable.
4562+
if (TemplateDecl *templateDecl =
4563+
cast<TemplateSpecializationType>(type.getTypePtr())
4564+
->getTemplateName()
4565+
.getAsTemplateDecl())
4566+
if (auto *CTD = dyn_cast<ClassTemplateDecl>(templateDecl))
4567+
return CTD->getTemplatedDecl()->hasAttr<TypeNullableAttr>();
45694568
return ResultIfUnknown;
45704569

45714570
case Type::Builtin:
@@ -4622,6 +4621,17 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
46224621
}
46234622
llvm_unreachable("unknown builtin type");
46244623

4624+
case Type::Record: {
4625+
const RecordDecl *RD = cast<RecordType>(type)->getDecl();
4626+
// For template specializations, look only at primary template attributes.
4627+
// This is a consistent regardless of whether the instantiation is known.
4628+
if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
4629+
return CTSD->getSpecializedTemplate()
4630+
->getTemplatedDecl()
4631+
->hasAttr<TypeNullableAttr>();
4632+
return RD->hasAttr<TypeNullableAttr>();
4633+
}
4634+
46254635
// Non-pointer types.
46264636
case Type::Complex:
46274637
case Type::LValueReference:
@@ -4639,7 +4649,6 @@ bool Type::canHaveNullability(bool ResultIfUnknown) const {
46394649
case Type::DependentAddressSpace:
46404650
case Type::FunctionProto:
46414651
case Type::FunctionNoProto:
4642-
case Type::Record:
46434652
case Type::DeducedTemplateSpecialization:
46444653
case Type::Enum:
46454654
case Type::InjectedClassName:

clang/lib/CodeGen/CGCall.cpp

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

43754375
bool CanCheckNullability = false;
4376-
if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD) {
4376+
if (SanOpts.has(SanitizerKind::NullabilityArg) && !NNAttr && PVD &&
4377+
!PVD->getType()->isRecordType()) {
43774378
auto Nullability = PVD->getType()->getNullability();
43784379
CanCheckNullability = Nullability &&
43794380
*Nullability == NullabilityKind::NonNull &&

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
974974
// return value. Initialize the flag to 'true' and refine it in EmitParmDecl.
975975
if (SanOpts.has(SanitizerKind::NullabilityReturn)) {
976976
auto Nullability = FnRetTy->getNullability();
977-
if (Nullability && *Nullability == NullabilityKind::NonNull) {
977+
if (Nullability && *Nullability == NullabilityKind::NonNull &&
978+
!FnRetTy->isRecordType()) {
978979
if (!(SanOpts.has(SanitizerKind::ReturnsNonnullAttribute) &&
979980
CurCodeDecl && CurCodeDecl->getAttr<ReturnsNonNullAttr>()))
980981
RetValNullabilityPrecondition =

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,15 @@ void Parser::ParseMicrosoftInheritanceClassAttributes(ParsedAttributes &attrs) {
14941494
}
14951495
}
14961496

1497+
void Parser::ParseNullabilityClassAttributes(ParsedAttributes &attrs) {
1498+
while (Tok.is(tok::kw__Nullable)) {
1499+
IdentifierInfo *AttrName = Tok.getIdentifierInfo();
1500+
auto Kind = Tok.getKind();
1501+
SourceLocation AttrNameLoc = ConsumeToken();
1502+
attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, Kind);
1503+
}
1504+
}
1505+
14971506
/// Determine whether the following tokens are valid after a type-specifier
14981507
/// which could be a standalone declaration. This will conservatively return
14991508
/// true if there's any doubt, and is appropriate for insert-';' fixits.
@@ -1675,15 +1684,21 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
16751684

16761685
ParsedAttributes attrs(AttrFactory);
16771686
// If attributes exist after tag, parse them.
1678-
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1679-
1680-
// Parse inheritance specifiers.
1681-
if (Tok.isOneOf(tok::kw___single_inheritance, tok::kw___multiple_inheritance,
1682-
tok::kw___virtual_inheritance))
1683-
ParseMicrosoftInheritanceClassAttributes(attrs);
1684-
1685-
// Allow attributes to precede or succeed the inheritance specifiers.
1686-
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1687+
for (;;) {
1688+
MaybeParseAttributes(PAKM_CXX11 | PAKM_Declspec | PAKM_GNU, attrs);
1689+
// Parse inheritance specifiers.
1690+
if (Tok.isOneOf(tok::kw___single_inheritance,
1691+
tok::kw___multiple_inheritance,
1692+
tok::kw___virtual_inheritance)) {
1693+
ParseMicrosoftInheritanceClassAttributes(attrs);
1694+
continue;
1695+
}
1696+
if (Tok.is(tok::kw__Nullable)) {
1697+
ParseNullabilityClassAttributes(attrs);
1698+
continue;
1699+
}
1700+
break;
1701+
}
16871702

16881703
// Source location used by FIXIT to insert misplaced
16891704
// 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"
@@ -7154,6 +7155,14 @@ bool Sema::getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember,
71547155
///
71557156
/// Returns true if the value evaluates to null.
71567157
static bool CheckNonNullExpr(Sema &S, const Expr *Expr) {
7158+
// Treat (smart) pointers constructed from nullptr as null, whether we can
7159+
// const-evaluate them or not.
7160+
// This must happen first: the smart pointer expr might have _Nonnull type!
7161+
if (isa<CXXNullPtrLiteralExpr>(
7162+
IgnoreExprNodes(Expr, IgnoreImplicitAsWrittenSingleStep,
7163+
IgnoreElidableImplicitConstructorSingleStep)))
7164+
return true;
7165+
71577166
// If the expression has non-null type, it doesn't evaluate to null.
71587167
if (auto nullability = Expr->IgnoreImplicit()->getType()->getNullability()) {
71597168
if (*nullability == NullabilityKind::NonNull)

clang/lib/Sema/SemaDecl.cpp

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

18174-
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New))
18174+
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New)) {
1817518175
inferGslOwnerPointerAttribute(CXXRD);
18176+
inferNullableClassAttribute(CXXRD);
18177+
}
1817618178

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

clang/lib/Sema/SemaDeclAttr.cpp

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

5958+
static void handleNullableTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
5959+
if (AL.isUsedAsTypeAttr())
5960+
return;
5961+
5962+
if (auto *CRD = dyn_cast<CXXRecordDecl>(D);
5963+
!D || !(CRD->isClass() || CRD->isStruct())) {
5964+
S.Diag(AL.getRange().getBegin(), diag::err_attribute_wrong_decl_type_str)
5965+
<< AL << AL.isRegularKeywordAttribute() << "classes";
5966+
return;
5967+
}
5968+
5969+
handleSimpleAttribute<TypeNullableAttr>(S, D, AL);
5970+
}
5971+
59585972
static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
59595973
if (!AL.hasParsedType()) {
59605974
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,
98629876
case ParsedAttr::AT_UsingIfExists:
98639877
handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
98649878
break;
9879+
9880+
case ParsedAttr::AT_TypeNullable:
9881+
handleNullableTypeAttr(S, D, AL);
9882+
break;
98659883
}
98669884
}
98679885

clang/lib/Sema/SemaInit.cpp

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

7053+
// A smart pointer constructed from a nullable pointer is nullable.
7054+
if (NumArgs == 1 && !Kind.isExplicitCast())
7055+
S.diagnoseNullableToNonnullConversion(Entity.getType(), Args.front()->getType(),
7056+
Kind.getLocation());
7057+
70537058
// Determine the arguments required to actually perform the constructor
70547059
// call.
70557060
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
@@ -14706,6 +14706,13 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1470614706
}
1470714707
}
1470814708

14709+
// Check for nonnull = nullable.
14710+
// This won't be caught in the arg's initialization: the parameter to
14711+
// the assignment operator is not marked nonnull.
14712+
if (Op == OO_Equal)
14713+
diagnoseNullableToNonnullConversion(Args[0]->getType(),
14714+
Args[1]->getType(), OpLoc);
14715+
1470914716
// Convert the arguments.
1471014717
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
1471114718
// 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
@@ -2164,6 +2164,7 @@ DeclResult Sema::CheckClassTemplate(
21642164

21652165
AddPushedVisibilityAttribute(NewClass);
21662166
inferGslOwnerPointerAttribute(NewClass);
2167+
inferNullableClassAttribute(NewClass);
21672168

21682169
if (TUK != TUK_Friend) {
21692170
// 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
@@ -4705,6 +4705,18 @@ static bool DiagnoseMultipleAddrSpaceAttributes(Sema &S, LangAS ASOld,
47054705
return false;
47064706
}
47074707

4708+
// Whether this is a type broadly expected to have nullability attached.
4709+
// These types are affected by `#pragma assume_nonnull`, and missing nullability
4710+
// will be diagnosed with -Wnullability-completeness.
4711+
static bool shouldHaveNullability(QualType T) {
4712+
return T->canHaveNullability(/*ResultIfUnknown=*/false) &&
4713+
// For now, do not infer/require nullability on C++ smart pointers.
4714+
// It's unclear whether the pragma's behavior is useful for C++.
4715+
// e.g. treating type-aliases and template-type-parameters differently
4716+
// from types of declarations can be surprising.
4717+
!isa<RecordType>(T);
4718+
}
4719+
47084720
static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
47094721
QualType declSpecType,
47104722
TypeSourceInfo *TInfo) {
@@ -4823,8 +4835,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
48234835
// inner pointers.
48244836
complainAboutMissingNullability = CAMN_InnerPointers;
48254837

4826-
if (T->canHaveNullability(/*ResultIfUnknown*/ false) &&
4827-
!T->getNullability()) {
4838+
if (shouldHaveNullability(T) && !T->getNullability()) {
48284839
// Note that we allow but don't require nullability on dependent types.
48294840
++NumPointersRemaining;
48304841
}
@@ -5047,8 +5058,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
50475058
// If the type itself could have nullability but does not, infer pointer
50485059
// nullability and perform consistency checking.
50495060
if (S.CodeSynthesisContexts.empty()) {
5050-
if (T->canHaveNullability(/*ResultIfUnknown*/ false) &&
5051-
!T->getNullability()) {
5061+
if (shouldHaveNullability(T) && !T->getNullability()) {
50525062
if (isVaList(T)) {
50535063
// Record that we've seen a pointer, but do nothing else.
50545064
if (NumPointersRemaining > 0)

0 commit comments

Comments
 (0)