From: Douglas Gregor Date: Fri, 19 Jun 2015 17:51:05 +0000 (+0000) Subject: Introduce type nullability specifiers for C/C++. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a46fb43c1f90054ea0c9a802fc7fb730499c9494;p=clang Introduce type nullability specifiers for C/C++. Introduces the type specifiers __nonnull, __nullable, and __null_unspecified that describe the nullability of the pointer type to which the specifier appertains. Nullability type specifiers improve on the existing nonnull attributes in a few ways: - They apply to types, so one can represent a pointer to a non-null pointer, use them in function pointer types, etc. - As type specifiers, they are syntactically more lightweight than __attribute__s or [[attribute]]s. - They can express both the notion of 'should never be null' and also 'it makes sense for this to be null', and therefore can more easily catch errors of omission where one forgot to annotate the nullability of a particular pointer (this will come in a subsequent patch). Nullability type specifiers are maintained as type sugar, and therefore have no effect on mangling, encoding, overloading, etc. Nonetheless, they will be used for warnings about, e.g., passing 'null' to a method that does not accept it. This is the C/C++ part of rdar://problem/18868820. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@240146 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h index 8cd29b7b91..d821258736 100644 --- a/include/clang/AST/Type.h +++ b/include/clang/AST/Type.h @@ -1818,6 +1818,19 @@ public: /// checking. Should always return true. bool isLinkageValid() const; + /// Determine the nullability of the given type. + /// + /// Note that nullability is only captured as sugar within the type + /// system, not as part of the canonical type, so nullability will + /// be lost by canonicalization and desugaring. + Optional getNullability(const ASTContext &context) const; + + /// Determine whether the given type can have a nullability + /// specifier applied to it, i.e., if it is any kind of pointer type + /// or a dependent type that could instantiate to any kind of + /// pointer type. + bool canHaveNullability() const; + const char *getTypeClassName() const; QualType getCanonicalTypeInternal() const { @@ -3479,7 +3492,10 @@ public: attr_ptr32, attr_ptr64, attr_sptr, - attr_uptr + attr_uptr, + attr_nonnull, + attr_nullable, + attr_null_unspecified, }; private: @@ -3513,6 +3529,34 @@ public: bool isCallingConv() const; + llvm::Optional getImmediateNullability() const; + + /// Retrieve the attribute kind corresponding to the given + /// nullability kind. + static Kind getNullabilityAttrKind(NullabilityKind kind) { + switch (kind) { + case NullabilityKind::NonNull: + return attr_nonnull; + + case NullabilityKind::Nullable: + return attr_nullable; + + case NullabilityKind::Unspecified: + return attr_null_unspecified; + } + } + + /// Strip off the top-level nullability annotation on the given + /// type, if it's there. + /// + /// \param T The type to strip. If the type is exactly an + /// AttributedType specifying nullability (without looking through + /// type sugar), the nullability is returned and this type changed + /// to the underlying modified type. + /// + /// \returns the top-level nullability, if present. + static Optional stripOuterNullability(QualType &T); + void Profile(llvm::FoldingSetNodeID &ID) { Profile(ID, getAttrKind(), ModifiedType, EquivalentType); } diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index 8394655c4b..f36f654ff6 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -960,6 +960,22 @@ def ReturnsNonNull : InheritableAttr { let Documentation = [Undocumented]; } +// Nullability type attributes. +def TypeNonNull : TypeAttr { + let Spellings = [Keyword<"__nonnull">]; + let Documentation = [Undocumented]; +} + +def TypeNullable : TypeAttr { + let Spellings = [Keyword<"__nullable">]; + let Documentation = [Undocumented]; +} + +def TypeNullUnspecified : TypeAttr { + let Spellings = [Keyword<"__null_unspecified">]; + let Documentation = [Undocumented]; +} + def AssumeAligned : InheritableAttr { let Spellings = [GCC<"assume_aligned">]; let Subjects = SubjectList<[ObjCMethod, Function]>; diff --git a/include/clang/Basic/DiagnosticCommonKinds.td b/include/clang/Basic/DiagnosticCommonKinds.td index deb23f3149..8532594342 100644 --- a/include/clang/Basic/DiagnosticCommonKinds.td +++ b/include/clang/Basic/DiagnosticCommonKinds.td @@ -99,6 +99,20 @@ def err_enum_template : Error<"enumeration cannot be a template">; } +let CategoryName = "Nullability Issue" in { + +def warn_mismatched_nullability_attr : Warning< + "nullability specifier " + "'__%select{nonnull|nullable|null_unspecified}0' " + "conflicts with existing specifier " + "'__%select{nonnull|nullable|null_unspecified}1'">, + InGroup; + +def note_nullability_here : Note< + "'%select{__nonnull|__nullable|__null_unspecified}0' specified here">; + +} + // Sema && Lex def ext_c99_longlong : Extension< "'long long' is an extension when C99 mode is not enabled">, diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index 13dcc73dd1..84ffe09bbe 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -248,6 +248,8 @@ def MissingFieldInitializers : DiagGroup<"missing-field-initializers">; def ModuleBuild : DiagGroup<"module-build">; def ModuleConflict : DiagGroup<"module-conflict">; def NewlineEOF : DiagGroup<"newline-eof">; +def Nullability : DiagGroup<"nullability">; +def NullabilityDeclSpec : DiagGroup<"nullability-declspec">; def NullArithmetic : DiagGroup<"null-arithmetic">; def NullCharacter : DiagGroup<"null-character">; def NullDereference : DiagGroup<"null-dereference">; diff --git a/include/clang/Basic/DiagnosticParseKinds.td b/include/clang/Basic/DiagnosticParseKinds.td index 3df8b949a1..6a11d240c6 100644 --- a/include/clang/Basic/DiagnosticParseKinds.td +++ b/include/clang/Basic/DiagnosticParseKinds.td @@ -65,6 +65,10 @@ def ext_keyword_as_ident : ExtWarn< "%select{here|for the remainder of the translation unit}1">, InGroup; +def ext_nullability : Extension< + "type nullability specifier %0 is a Clang extension">, + InGroup>; + def error_empty_enum : Error<"use of empty enum">; def err_invalid_sign_spec : Error<"'%0' cannot be signed or unsigned">; def err_invalid_short_spec : Error<"'short %0' is invalid">; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index b8fadda854..ac534436e7 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -7674,4 +7674,32 @@ def warn_profile_data_unprofiled : Warning< } // end of instrumentation issue category +let CategoryName = "Nullability Issue" in { + +def warn_duplicate_nullability : Warning< + "duplicate nullability specifier " + "'%select{__nonnull|__nullable|__null_unspecified}0'">, + InGroup; + +def warn_nullability_declspec : Warning< + "nullability specifier " + "'%select{__nonnull|__nullable|__null_unspecified}0' cannot be applied " + "to non-pointer type %1; did you mean to apply the specifier to the " + "%select{pointer|block pointer|member pointer|function pointer|" + "member function pointer}2?">, + InGroup, + DefaultError; + +def err_nullability_nonpointer : Error< + "nullability specifier " + "'%select{__nonnull|__nullable|__null_unspecified}0' cannot be applied to " + "non-pointer type %1">; + +def err_nullability_conflicting : Error< + "nullability specifier " + "'%select{__nonnull|__nullable|__null_unspecified}0' conflicts with existing " + "specifier '%select{__nonnull|__nullable|__null_unspecified}1'">; + +} + } // end of sema component. diff --git a/include/clang/Basic/Specifiers.h b/include/clang/Basic/Specifiers.h index 7569c16412..2a872b277e 100644 --- a/include/clang/Basic/Specifiers.h +++ b/include/clang/Basic/Specifiers.h @@ -16,6 +16,8 @@ #ifndef LLVM_CLANG_BASIC_SPECIFIERS_H #define LLVM_CLANG_BASIC_SPECIFIERS_H +#include "llvm/Support/DataTypes.h" + namespace clang { /// \brief Specifies the width of a type, e.g., short, long, or long long. enum TypeSpecifierWidth { @@ -239,6 +241,19 @@ namespace clang { SD_Static, ///< Static storage duration. SD_Dynamic ///< Dynamic storage duration. }; + + /// Describes the nullability of a particular type. + enum class NullabilityKind : uint8_t { + /// Values of this type can never be null. + NonNull = 0, + /// Values of this type can be null. + Nullable, + /// Whether values of this type can be null is (explicitly) + /// unspecified. This captures a (fairly rare) case where we + /// can't conclude anything about the nullability of the type even + /// though it has been considered. + Unspecified + }; } // end namespace clang #endif // LLVM_CLANG_BASIC_SPECIFIERS_H diff --git a/include/clang/Basic/TokenKinds.def b/include/clang/Basic/TokenKinds.def index b6d983b552..67b9933562 100644 --- a/include/clang/Basic/TokenKinds.def +++ b/include/clang/Basic/TokenKinds.def @@ -548,6 +548,11 @@ ALIAS("__typeof__" , typeof , KEYALL) ALIAS("__volatile" , volatile , KEYALL) ALIAS("__volatile__" , volatile , KEYALL) +// Type nullability. +KEYWORD(__nonnull , KEYALL) +KEYWORD(__nullable , KEYALL) +KEYWORD(__null_unspecified , KEYALL) + // Microsoft extensions which should be disabled in strict conformance mode KEYWORD(__ptr64 , KEYMS) KEYWORD(__ptr32 , KEYMS) diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index caba77b74c..3d48f0ad5c 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -2106,6 +2106,7 @@ private: void ParseBorlandTypeAttributes(ParsedAttributes &attrs); void ParseOpenCLAttributes(ParsedAttributes &attrs); void ParseOpenCLQualifiers(ParsedAttributes &Attrs); + void ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs); VersionTuple ParseVersionTuple(SourceRange &Range); void ParseAvailabilityAttribute(IdentifierInfo &Availability, diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 09bb769259..15f3d39634 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -1919,7 +1919,10 @@ bool AttributedType::isCallingConv() const { case attr_objc_gc: case attr_objc_ownership: case attr_noreturn: - return false; + case attr_nonnull: + case attr_nullable: + case attr_null_unspecified: + return false; case attr_pcs: case attr_pcs_vfp: case attr_cdecl: @@ -2340,6 +2343,152 @@ LinkageInfo Type::getLinkageAndVisibility() const { return LV; } +Optional Type::getNullability(const ASTContext &context) const { + QualType type(this, 0); + do { + // Check whether this is an attributed type with nullability + // information. + if (auto attributed = dyn_cast(type.getTypePtr())) { + if (auto nullability = attributed->getImmediateNullability()) + return nullability; + } + + // Desugar the type. If desugaring does nothing, we're done. + QualType desugared = type.getSingleStepDesugaredType(context); + if (desugared.getTypePtr() == type.getTypePtr()) + return None; + + type = desugared; + } while (true); +} + +bool Type::canHaveNullability() const { + QualType type = getCanonicalTypeInternal(); + + switch (type->getTypeClass()) { + // We'll only see canonical types here. +#define NON_CANONICAL_TYPE(Class, Parent) \ + case Type::Class: \ + llvm_unreachable("non-canonical type"); +#define TYPE(Class, Parent) +#include "clang/AST/TypeNodes.def" + + // Pointer types. + case Type::Pointer: + case Type::BlockPointer: + case Type::MemberPointer: + case Type::ObjCObjectPointer: + return true; + + // Dependent types that could instantiate to pointer types. + case Type::UnresolvedUsing: + case Type::TypeOfExpr: + case Type::TypeOf: + case Type::Decltype: + case Type::UnaryTransform: + case Type::TemplateTypeParm: + case Type::SubstTemplateTypeParmPack: + case Type::DependentName: + case Type::DependentTemplateSpecialization: + return true; + + // Dependent template specializations can instantiate to pointer + // types unless they're known to be specializations of a class + // template. + case Type::TemplateSpecialization: + if (TemplateDecl *templateDecl + = cast(type.getTypePtr()) + ->getTemplateName().getAsTemplateDecl()) { + if (isa(templateDecl)) + return false; + } + return true; + + // auto is considered dependent when it isn't deduced. + case Type::Auto: + return !cast(type.getTypePtr())->isDeduced(); + + case Type::Builtin: + switch (cast(type.getTypePtr())->getKind()) { + // Signed, unsigned, and floating-point types cannot have nullability. +#define SIGNED_TYPE(Id, SingletonId) case BuiltinType::Id: +#define UNSIGNED_TYPE(Id, SingletonId) case BuiltinType::Id: +#define FLOATING_TYPE(Id, SingletonId) case BuiltinType::Id: +#define BUILTIN_TYPE(Id, SingletonId) +#include "clang/AST/BuiltinTypes.def" + return false; + + // Dependent types that could instantiate to a pointer type. + case BuiltinType::Dependent: + case BuiltinType::Overload: + case BuiltinType::BoundMember: + case BuiltinType::PseudoObject: + case BuiltinType::UnknownAny: + case BuiltinType::ARCUnbridgedCast: + return true; + + case BuiltinType::Void: + case BuiltinType::ObjCId: + case BuiltinType::ObjCClass: + case BuiltinType::ObjCSel: + case BuiltinType::OCLImage1d: + case BuiltinType::OCLImage1dArray: + case BuiltinType::OCLImage1dBuffer: + case BuiltinType::OCLImage2d: + case BuiltinType::OCLImage2dArray: + case BuiltinType::OCLImage3d: + case BuiltinType::OCLSampler: + case BuiltinType::OCLEvent: + case BuiltinType::BuiltinFn: + case BuiltinType::NullPtr: + return false; + } + + // Non-pointer types. + case Type::Complex: + case Type::LValueReference: + case Type::RValueReference: + case Type::ConstantArray: + case Type::IncompleteArray: + case Type::VariableArray: + case Type::DependentSizedArray: + case Type::DependentSizedExtVector: + case Type::Vector: + case Type::ExtVector: + case Type::FunctionProto: + case Type::FunctionNoProto: + case Type::Record: + case Type::Enum: + case Type::InjectedClassName: + case Type::PackExpansion: + case Type::ObjCObject: + case Type::ObjCInterface: + case Type::Atomic: + return false; + } +} + +llvm::Optional AttributedType::getImmediateNullability() const { + if (getAttrKind() == AttributedType::attr_nonnull) + return NullabilityKind::NonNull; + if (getAttrKind() == AttributedType::attr_nullable) + return NullabilityKind::Nullable; + if (getAttrKind() == AttributedType::attr_null_unspecified) + return NullabilityKind::Unspecified; + return None; +} + +Optional AttributedType::stripOuterNullability(QualType &T) { + if (auto attributed = dyn_cast(T.getTypePtr())) { + if (auto nullability = attributed->getImmediateNullability()) { + T = attributed->getModifiedType(); + return nullability; + } + } + + return None; +} + Qualifiers::ObjCLifetime Type::getObjCARCImplicitLifetime() const { if (isObjCARCImplicitlyUnretainedType()) return Qualifiers::OCL_ExplicitNone; diff --git a/lib/AST/TypePrinter.cpp b/lib/AST/TypePrinter.cpp index 3928fe8f89..ebe09d8549 100644 --- a/lib/AST/TypePrinter.cpp +++ b/lib/AST/TypePrinter.cpp @@ -1141,6 +1141,21 @@ void TypePrinter::printAttributedBefore(const AttributedType *T, } spaceBeforePlaceHolder(OS); } + + // Print nullability type specifiers. + if (T->getAttrKind() == AttributedType::attr_nonnull || + T->getAttrKind() == AttributedType::attr_nullable || + T->getAttrKind() == AttributedType::attr_null_unspecified) { + if (T->getAttrKind() == AttributedType::attr_nonnull) + OS << " __nonnull"; + else if (T->getAttrKind() == AttributedType::attr_nullable) + OS << " __nullable"; + else if (T->getAttrKind() == AttributedType::attr_null_unspecified) + OS << " __null_unspecified"; + else + llvm_unreachable("unhandled nullability"); + spaceBeforePlaceHolder(OS); + } } void TypePrinter::printAttributedAfter(const AttributedType *T, @@ -1154,12 +1169,34 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, if (T->isMSTypeSpec()) return; + // Nothing to print after. + if (T->getAttrKind() == AttributedType::attr_nonnull || + T->getAttrKind() == AttributedType::attr_nullable || + T->getAttrKind() == AttributedType::attr_null_unspecified) + return printAfter(T->getModifiedType(), OS); + // If this is a calling convention attribute, don't print the implicit CC from // the modified type. SaveAndRestore MaybeSuppressCC(InsideCCAttribute, T->isCallingConv()); printAfter(T->getModifiedType(), OS); + // Print nullability type specifiers that occur after + if (T->getAttrKind() == AttributedType::attr_nonnull || + T->getAttrKind() == AttributedType::attr_nullable || + T->getAttrKind() == AttributedType::attr_null_unspecified) { + if (T->getAttrKind() == AttributedType::attr_nonnull) + OS << " __nonnull"; + else if (T->getAttrKind() == AttributedType::attr_nullable) + OS << " __nullable"; + else if (T->getAttrKind() == AttributedType::attr_null_unspecified) + OS << " __null_unspecified"; + else + llvm_unreachable("unhandled nullability"); + + return; + } + OS << " __attribute__(("; switch (T->getAttrKind()) { default: llvm_unreachable("This attribute should have been handled already"); diff --git a/lib/Lex/PPMacroExpansion.cpp b/lib/Lex/PPMacroExpansion.cpp index ccfb30240b..62498c6446 100644 --- a/lib/Lex/PPMacroExpansion.cpp +++ b/lib/Lex/PPMacroExpansion.cpp @@ -1075,6 +1075,7 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) { .Case("cxx_exceptions", LangOpts.CXXExceptions) .Case("cxx_rtti", LangOpts.RTTI) .Case("enumerator_attributes", true) + .Case("nullability", LangOpts.ObjC1 || LangOpts.GNUMode) .Case("memory_sanitizer", LangOpts.Sanitize.has(SanitizerKind::Memory)) .Case("thread_sanitizer", LangOpts.Sanitize.has(SanitizerKind::Thread)) .Case("dataflow_sanitizer", LangOpts.Sanitize.has(SanitizerKind::DataFlow)) @@ -1222,6 +1223,7 @@ static bool HasExtension(const Preprocessor &PP, const IdentifierInfo *II) { // Because we inherit the feature list from HasFeature, this string switch // must be less restrictive than HasFeature's. return llvm::StringSwitch(Extension) + .Case("nullability", true) // C11 features supported by other languages as extensions. .Case("c_alignas", true) .Case("c_alignof", true) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index eb6800d65c..96555fcf87 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -687,6 +687,28 @@ void Parser::ParseOpenCLQualifiers(ParsedAttributes &Attrs) { AttributeList::AS_Keyword); } +void Parser::ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs) { + // Treat these like attributes, even though they're type specifiers. + while (true) { + switch (Tok.getKind()) { + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: { + IdentifierInfo *AttrName = Tok.getIdentifierInfo(); + SourceLocation AttrNameLoc = ConsumeToken(); + if (!getLangOpts().ObjC1) + Diag(AttrNameLoc, diag::ext_nullability) + << AttrName; + attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, + AttributeList::AS_Keyword); + break; + } + default: + return; + } + } +} + static bool VersionNumberSeparator(const char Separator) { return (Separator == '.' || Separator == '_'); } @@ -3040,6 +3062,13 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, ParseOpenCLAttributes(DS.getAttributes()); continue; + // Nullability type specifiers. + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: + ParseNullabilityTypeSpecifiers(DS.getAttributes()); + continue; + // storage-class-specifier case tok::kw_typedef: isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_typedef, Loc, @@ -4284,6 +4313,10 @@ bool Parser::isTypeSpecifierQualifier() { case tok::kw___pascal: case tok::kw___unaligned: + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: + case tok::kw___private: case tok::kw___local: case tok::kw___global: @@ -4457,6 +4490,10 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { case tok::kw___pascal: case tok::kw___unaligned: + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: + case tok::kw___private: case tok::kw___local: case tok::kw___global: @@ -4686,6 +4723,14 @@ void Parser::ParseTypeQualifierListOpt(DeclSpec &DS, unsigned AttrReqs, continue; } goto DoneWithTypeQuals; + + // Nullability type specifiers. + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: + ParseNullabilityTypeSpecifiers(DS.getAttributes()); + continue; + case tok::kw___attribute: if (AttrReqs & AR_GNUAttributesParsedAndRejected) // When GNU attributes are expressly forbidden, diagnose their usage. diff --git a/lib/Parse/ParseTentative.cpp b/lib/Parse/ParseTentative.cpp index e21409a62f..d63cf24bcd 100644 --- a/lib/Parse/ParseTentative.cpp +++ b/lib/Parse/ParseTentative.cpp @@ -631,7 +631,9 @@ Parser::TPResult Parser::TryParsePtrOperatorSeq() { (Tok.is(tok::annot_cxxscope) && NextToken().is(tok::star))) { // ptr-operator ConsumeToken(); - while (Tok.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict)) + while (Tok.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict, + tok::kw___nonnull, tok::kw___nullable, + tok::kw___null_unspecified)) ConsumeToken(); } else { return TPResult::True; @@ -1274,6 +1276,9 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult, case tok::kw___ptr32: case tok::kw___forceinline: case tok::kw___unaligned: + case tok::kw___nonnull: + case tok::kw___nullable: + case tok::kw___null_unspecified: return TPResult::True; // Borland diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 73bdd984c0..f4af3f320a 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -2465,6 +2465,28 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl, if (!foundAny) newDecl->dropAttrs(); } +static void mergeParamDeclTypes(ParmVarDecl *NewParam, + const ParmVarDecl *OldParam, + Sema &S) { + if (auto Oldnullability = OldParam->getType()->getNullability(S.Context)) { + if (auto Newnullability = NewParam->getType()->getNullability(S.Context)) { + if (*Oldnullability != *Newnullability) { + S.Diag(NewParam->getLocation(), diag::warn_mismatched_nullability_attr) + << static_cast(*Newnullability) + << static_cast(*Oldnullability); + S.Diag(OldParam->getLocation(), diag::note_previous_declaration); + } + } + else { + QualType NewT = NewParam->getType(); + NewT = S.Context.getAttributedType( + AttributedType::getNullabilityAttrKind(*Oldnullability), + NewT, NewT); + NewParam->setType(NewT); + } + } +} + namespace { /// Used in MergeFunctionDecl to keep track of function parameters in @@ -3101,9 +3123,12 @@ bool Sema::MergeCompatibleFunctionDecls(FunctionDecl *New, FunctionDecl *Old, // Merge attributes from the parameters. These can mismatch with K&R // declarations. if (New->getNumParams() == Old->getNumParams()) - for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) - mergeParamDeclAttributes(New->getParamDecl(i), Old->getParamDecl(i), - *this); + for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) { + ParmVarDecl *NewParam = New->getParamDecl(i); + ParmVarDecl *OldParam = Old->getParamDecl(i); + mergeParamDeclAttributes(NewParam, OldParam, *this); + mergeParamDeclTypes(NewParam, OldParam, *this); + } if (getLangOpts().CPlusPlus) return MergeCXXFunctionDecl(New, Old, S); diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index d3787ec44b..ea749a46e0 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -22,6 +22,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/TypeLoc.h" #include "clang/AST/TypeLocVisitor.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Basic/PartialDiagnostic.h" #include "clang/Basic/TargetInfo.h" #include "clang/Parse/ParseDiagnostic.h" @@ -121,6 +122,12 @@ static void diagnoseBadTypeAttribute(Sema &S, const AttributeList &attr, case AttributeList::AT_SPtr: \ case AttributeList::AT_UPtr +// Nullability qualifiers. +#define NULLABILITY_TYPE_ATTRS_CASELIST \ + case AttributeList::AT_TypeNonNull: \ + case AttributeList::AT_TypeNullable: \ + case AttributeList::AT_TypeNullUnspecified + namespace { /// An object which stores processing state for the entire /// GetTypeForDeclarator process. @@ -307,8 +314,12 @@ static bool handleObjCPointerTypeAttr(TypeProcessingState &state, /// /// \param i - a notional index which the search will start /// immediately inside +/// +/// \param onlyBlockPointers Whether we should only look into block +/// pointer types (vs. all pointer types). static DeclaratorChunk *maybeMovePastReturnType(Declarator &declarator, - unsigned i) { + unsigned i, + bool onlyBlockPointers) { assert(i <= declarator.getNumTypeObjects()); DeclaratorChunk *result = nullptr; @@ -329,20 +340,26 @@ static DeclaratorChunk *maybeMovePastReturnType(Declarator &declarator, return result; // If we do find a function declarator, scan inwards from that, - // looking for a block-pointer declarator. + // looking for a (block-)pointer declarator. case DeclaratorChunk::Function: for (--i; i != 0; --i) { - DeclaratorChunk &blockChunk = declarator.getTypeObject(i-1); - switch (blockChunk.Kind) { + DeclaratorChunk &ptrChunk = declarator.getTypeObject(i-1); + switch (ptrChunk.Kind) { case DeclaratorChunk::Paren: - case DeclaratorChunk::Pointer: case DeclaratorChunk::Array: case DeclaratorChunk::Function: case DeclaratorChunk::Reference: - case DeclaratorChunk::MemberPointer: continue; + + case DeclaratorChunk::MemberPointer: + case DeclaratorChunk::Pointer: + if (onlyBlockPointers) + continue; + + // fallthrough + case DeclaratorChunk::BlockPointer: - result = &blockChunk; + result = &ptrChunk; goto continue_outer; } llvm_unreachable("bad declarator chunk kind"); @@ -382,7 +399,8 @@ static void distributeObjCPointerTypeAttr(TypeProcessingState &state, DeclaratorChunk *destChunk = nullptr; if (state.isProcessingDeclSpec() && attr.getKind() == AttributeList::AT_ObjCOwnership) - destChunk = maybeMovePastReturnType(declarator, i - 1); + destChunk = maybeMovePastReturnType(declarator, i - 1, + /*onlyBlockPointers=*/true); if (!destChunk) destChunk = &chunk; moveAttrFromListToList(attr, state.getCurrentAttrListRef(), @@ -398,7 +416,9 @@ static void distributeObjCPointerTypeAttr(TypeProcessingState &state, case DeclaratorChunk::Function: if (state.isProcessingDeclSpec() && attr.getKind() == AttributeList::AT_ObjCOwnership) { - if (DeclaratorChunk *dest = maybeMovePastReturnType(declarator, i)) { + if (DeclaratorChunk *dest = maybeMovePastReturnType( + declarator, i, + /*onlyBlockPointers=*/true)) { moveAttrFromListToList(attr, state.getCurrentAttrListRef(), dest->getAttrListRef()); return; @@ -620,6 +640,10 @@ static void distributeTypeAttrsFromDeclarator(TypeProcessingState &state, // Microsoft type attributes cannot go after the declarator-id. continue; + NULLABILITY_TYPE_ATTRS_CASELIST: + // Nullability specifiers cannot go after the declarator-id. + continue; + default: break; } @@ -3495,6 +3519,12 @@ static AttributeList::Kind getAttrListKind(AttributedType::Kind kind) { return AttributeList::AT_SPtr; case AttributedType::attr_uptr: return AttributeList::AT_UPtr; + case AttributedType::attr_nonnull: + return AttributeList::AT_TypeNonNull; + case AttributedType::attr_nullable: + return AttributeList::AT_TypeNullable; + case AttributedType::attr_null_unspecified: + return AttributeList::AT_TypeNullUnspecified; } llvm_unreachable("unexpected attribute kind!"); } @@ -4114,7 +4144,8 @@ static bool handleObjCOwnershipTypeAttr(TypeProcessingState &state, // just be the return type of a block pointer. if (state.isProcessingDeclSpec()) { Declarator &D = state.getDeclarator(); - if (maybeMovePastReturnType(D, D.getNumTypeObjects())) + if (maybeMovePastReturnType(D, D.getNumTypeObjects(), + /*onlyBlockPointers=*/true)) return false; } } @@ -4491,6 +4522,205 @@ static bool handleMSPointerTypeQualifierAttr(TypeProcessingState &State, return false; } +/// Map a nullability attribute kind to a nullability kind. +static NullabilityKind mapNullabilityAttrKind(AttributeList::Kind kind) { + switch (kind) { + case AttributeList::AT_TypeNonNull: + return NullabilityKind::NonNull; + + case AttributeList::AT_TypeNullable: + return NullabilityKind::Nullable; + + case AttributeList::AT_TypeNullUnspecified: + return NullabilityKind::Unspecified; + + default: + llvm_unreachable("not a nullability attribute kind"); + } +} + +/// Handle a nullability type attribute. +static bool handleNullabilityTypeAttr(TypeProcessingState &state, + AttributeList &attr, + QualType &type) { + Sema &S = state.getSema(); + ASTContext &Context = S.Context; + + // Determine the nullability. + AttributeList::Kind kind = attr.getKind(); + NullabilityKind nullability = mapNullabilityAttrKind(kind); + + // Check for existing nullability attributes on the type. + QualType desugared = type; + while (auto attributed = dyn_cast(desugared.getTypePtr())) { + // Check whether there is already a null + if (auto existingNullability = attributed->getImmediateNullability()) { + // Duplicated nullability. + if (nullability == *existingNullability) { + S.Diag(attr.getLoc(), diag::warn_duplicate_nullability) + << static_cast(nullability); + return true; + } + + // Conflicting nullability. + S.Diag(attr.getLoc(), diag::err_nullability_conflicting) + << static_cast(nullability) + << static_cast(*existingNullability); + return true; + } + + desugared = attributed->getEquivalentType(); + } + + // If there is already a different nullability specifier, complain. + // This (unlike the code above) looks through typedefs that might + // have nullability specifiers on them, which means we cannot + // provide a useful Fix-It. + if (auto existingNullability = desugared->getNullability(Context)) { + if (nullability != *existingNullability) { + S.Diag(attr.getLoc(), diag::err_nullability_conflicting) + << static_cast(nullability) + << static_cast(*existingNullability); + + // Try to find the typedef with the existing nullability specifier. + if (auto typedefType = desugared->getAs()) { + TypedefNameDecl *typedefDecl = typedefType->getDecl(); + QualType underlyingType = typedefDecl->getUnderlyingType(); + if (auto typedefNullability + = AttributedType::stripOuterNullability(underlyingType)) { + if (*typedefNullability == *existingNullability) { + S.Diag(typedefDecl->getLocation(), diag::note_nullability_here) + << static_cast(*existingNullability); + } + } + } + + return true; + } + } + + // If this definitely isn't a pointer type, reject the specifier. + if (!type->canHaveNullability()) { + S.Diag(attr.getLoc(), diag::err_nullability_nonpointer) + << static_cast(nullability) << type; + return true; + } + + // Form the attributed type. + AttributedType::Kind typeAttrKind; + switch (kind) { + case AttributeList::AT_TypeNonNull: + typeAttrKind = AttributedType::attr_nonnull; + break; + + case AttributeList::AT_TypeNullable: + typeAttrKind = AttributedType::attr_nullable; + break; + + case AttributeList::AT_TypeNullUnspecified: + typeAttrKind = AttributedType::attr_null_unspecified; + break; + + default: + llvm_unreachable("Not a nullability specifier"); + } + type = S.Context.getAttributedType(typeAttrKind, type, type); + return false; +} + +/// Check whether there is a nullability attribute of any kind in the given +/// attribute list. +static bool hasNullabilityAttr(const AttributeList *attrs) { + for (const AttributeList *attr = attrs; attr; + attr = attr->getNext()) { + if (attr->getKind() == AttributeList::AT_TypeNonNull || + attr->getKind() == AttributeList::AT_TypeNullable || + attr->getKind() == AttributeList::AT_TypeNullUnspecified) + return true; + } + + return false; +} + +/// Distribute a nullability type attribute that cannot be applied to +/// the type specifier to a pointer, block pointer, or member pointer +/// declarator, complaining if necessary. +/// +/// \returns true if the nullability annotation was distributed, false +/// otherwise. +static bool distributeNullabilityTypeAttr(TypeProcessingState &state, + QualType type, + AttributeList &attr) { + Declarator &declarator = state.getDeclarator(); + + /// Attempt to move the attribute to the specified chunk. + auto moveToChunk = [&](DeclaratorChunk &chunk, bool inFunction) -> bool { + // If there is already a nullability attribute there, don't add + // one. + if (hasNullabilityAttr(chunk.getAttrListRef())) + return false; + + // Complain about the nullability qualifier being in the wrong + // place. + unsigned pointerKind + = chunk.Kind == DeclaratorChunk::Pointer ? (inFunction ? 3 : 0) + : chunk.Kind == DeclaratorChunk::BlockPointer ? 1 + : inFunction? 4 : 2; + + auto diag = state.getSema().Diag(attr.getLoc(), + diag::warn_nullability_declspec) + << static_cast(mapNullabilityAttrKind(attr.getKind())) + << type + << pointerKind; + + // FIXME: MemberPointer chunks don't carry the location of the *. + if (chunk.Kind != DeclaratorChunk::MemberPointer) { + diag << FixItHint::CreateRemoval(attr.getLoc()) + << FixItHint::CreateInsertion( + state.getSema().getPreprocessor() + .getLocForEndOfToken(chunk.Loc), + " " + attr.getName()->getName().str() + " "); + } + + moveAttrFromListToList(attr, state.getCurrentAttrListRef(), + chunk.getAttrListRef()); + return true; + }; + + // Move it to the outermost pointer, member pointer, or block + // pointer declarator. + for (unsigned i = state.getCurrentChunkIndex(); i != 0; --i) { + DeclaratorChunk &chunk = declarator.getTypeObject(i-1); + switch (chunk.Kind) { + case DeclaratorChunk::Pointer: + case DeclaratorChunk::BlockPointer: + case DeclaratorChunk::MemberPointer: + return moveToChunk(chunk, false); + + case DeclaratorChunk::Paren: + case DeclaratorChunk::Array: + continue; + + case DeclaratorChunk::Function: + // Try to move past the return type to a function/block/member + // function pointer. + if (DeclaratorChunk *dest = maybeMovePastReturnType( + declarator, i, + /*onlyBlockPointers=*/false)) { + return moveToChunk(*dest, true); + } + + return false; + + // Don't walk through these. + case DeclaratorChunk::Reference: + return false; + } + } + + return false; +} + static AttributedType::Kind getCCTypeAttrKind(AttributeList &Attr) { assert(!Attr.isInvalid()); switch (Attr.getKind()) { @@ -4997,6 +5227,20 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, attr.setUsedAsTypeAttr(); break; + NULLABILITY_TYPE_ATTRS_CASELIST: + // Either add nullability here or try to distribute it. We + // don't want to distribute the nullability specifier past any + // dependent type, because that complicates the user model. + if (type->canHaveNullability() || type->isDependentType() || + !distributeNullabilityTypeAttr(state, type, attr)) { + if (handleNullabilityTypeAttr(state, attr, type)) { + attr.setInvalid(); + } + + attr.setUsedAsTypeAttr(); + } + break; + case AttributeList::AT_NSReturnsRetained: if (!state.getSema().getLangOpts().ObjCAutoRefCount) break; diff --git a/lib/Sema/TreeTransform.h b/lib/Sema/TreeTransform.h index 878300ebc9..c4cd22609b 100644 --- a/lib/Sema/TreeTransform.h +++ b/lib/Sema/TreeTransform.h @@ -5386,6 +5386,17 @@ QualType TreeTransform::TransformAttributedType( = getDerived().TransformType(oldType->getEquivalentType()); if (equivalentType.isNull()) return QualType(); + + // Check whether we can add nullability; it is only represented as + // type sugar, and therefore cannot be diagnosed in any other way. + if (auto nullability = oldType->getImmediateNullability()) { + if (!modifiedType->canHaveNullability()) { + SemaRef.Diag(TL.getAttrNameLoc(), diag::err_nullability_nonpointer) + << static_cast(*nullability) << modifiedType; + return QualType(); + } + } + result = SemaRef.Context.getAttributedType(oldType->getAttrKind(), modifiedType, equivalentType); diff --git a/test/FixIt/fixit-nullability-declspec.cpp b/test/FixIt/fixit-nullability-declspec.cpp new file mode 100644 index 0000000000..2ac20b9d9b --- /dev/null +++ b/test/FixIt/fixit-nullability-declspec.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -fblocks -Werror=nullability-declspec -x c++ -verify %s + +// RUN: cp %s %t +// RUN: not %clang_cc1 -fixit -fblocks -Werror=nullability-declspec -x c++ %t +// RUN: %clang_cc1 -fblocks -Werror=nullability-declspec -x c++ %t + +__nullable int *ip1; // expected-error{{nullability specifier '__nullable' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the pointer?}} +__nullable int (*fp1)(int); // expected-error{{nullability specifier '__nullable' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the function pointer?}} +__nonnull int (^bp1)(int); // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the block pointer?}} diff --git a/test/Parser/nullability.c b/test/Parser/nullability.c new file mode 100644 index 0000000000..f2b6abf73d --- /dev/null +++ b/test/Parser/nullability.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c99 -Wno-nullability-declspec -pedantic %s -verify + +__nonnull int *ptr; // expected-warning{{type nullability specifier '__nonnull' is a Clang extension}} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-extension" +__nonnull int *ptr2; // no-warning +#pragma clang diagnostic pop + +#if __has_feature(nullability) +# error Nullability should not be supported in C under -pedantic -std=c99 +#endif + +#if !__has_extension(nullability) +# error Nullability should always be supported as an extension +#endif diff --git a/test/Sema/non-null-warning.c b/test/Sema/non-null-warning.c new file mode 100644 index 0000000000..0cabc713ba --- /dev/null +++ b/test/Sema/non-null-warning.c @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -fsyntax-only -Wnonnull -Wnullability %s -verify +// rdar://19160762 + +#if __has_feature(nullability) +#else +# error nullability feature should be defined +#endif + + +int * __nullable foo(int * __nonnull x); + +int *__nonnull ret_nonnull(); + +int *foo(int *x) { + return 0; +} + +int * __nullable foo1(int * __nonnull x); // expected-note {{previous declaration is here}} + +int *foo1(int * __nullable x) { // expected-warning {{nullability specifier '__nullable' conflicts with existing specifier '__nonnull'}} + return 0; +} + +int * __nullable foo2(int * __nonnull x); + +int *foo2(int * __nonnull x) { + return 0; +} + +int * __nullable foo3(int * __nullable x); // expected-note {{previous declaration is here}} + +int *foo3(int * __nonnull x) { // expected-warning {{nullability specifier '__nonnull' conflicts with existing specifier '__nullable'}} + return 0; +} + diff --git a/test/Sema/nullability.c b/test/Sema/nullability.c new file mode 100644 index 0000000000..bf35b4ca1a --- /dev/null +++ b/test/Sema/nullability.c @@ -0,0 +1,87 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -Wno-nullability-declspec %s -verify + +#if __has_feature(nullability) +#else +# error nullability feature should be defined +#endif + +typedef int * int_ptr; + +// Parse nullability type specifiers. +typedef int * __nonnull nonnull_int_ptr; // expected-note{{'__nonnull' specified here}} +typedef int * __nullable nullable_int_ptr; +typedef int * __null_unspecified null_unspecified_int_ptr; + +// Redundant nullability type specifiers. +typedef int * __nonnull __nonnull redundant_1; // expected-warning{{duplicate nullability specifier '__nonnull'}} + +// Conflicting nullability type specifiers. +typedef int * __nonnull __nullable conflicting_1; // expected-error{{nullability specifier '__nonnull' conflicts with existing specifier '__nullable'}} +typedef int * __null_unspecified __nonnull conflicting_2; // expected-error{{nullability specifier '__null_unspecified' conflicts with existing specifier '__nonnull'}} + +// Redundant nullability specifiers via a typedef are okay. +typedef nonnull_int_ptr __nonnull redundant_okay_1; + +// Conflicting nullability specifiers via a typedef are not. +typedef nonnull_int_ptr __nullable conflicting_2; // expected-error{{nullability specifier '__nullable' conflicts with existing specifier '__nonnull'}} +typedef nonnull_int_ptr nonnull_int_ptr_typedef; +typedef nonnull_int_ptr_typedef __nullable conflicting_2; // expected-error{{nullability specifier '__nullable' conflicts with existing specifier '__nonnull'}} +typedef nonnull_int_ptr_typedef nonnull_int_ptr_typedef_typedef; +typedef nonnull_int_ptr_typedef_typedef __null_unspecified conflicting_3; // expected-error{{nullability specifier '__null_unspecified' conflicts with existing specifier '__nonnull'}} + +// Nullability applies to all pointer types. +typedef int (* __nonnull function_pointer_type_1)(int, int); +typedef int (^ __nonnull block_type_1)(int, int); + +// Nullability must be on a pointer type. +typedef int __nonnull int_type_1; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'}} + +// Nullability can move out to a pointer/block pointer declarator +// (with a suppressed warning). +typedef __nonnull int * nonnull_int_ptr_2; +typedef int __nullable * nullable_int_ptr_2; +typedef __nonnull int (* function_pointer_type_2)(int, int); +typedef __nonnull int (^ block_type_2)(int, int); +typedef __nonnull int * * __nullable nonnull_int_ptr_ptr_1; +typedef __nonnull int *(^ block_type_3)(int, int); +typedef __nonnull int *(* function_pointer_type_3)(int, int); +typedef __nonnull int_ptr (^ block_type_4)(int, int); +typedef __nonnull int_ptr (* function_pointer_type_4)(int, int); + +void acceptFunctionPtr(__nonnull int *(*)(void)); +void acceptBlockPtr(__nonnull int *(^)(void)); + +void testBlockFunctionPtrNullability() { + float *fp; + fp = (function_pointer_type_3)0; // expected-warning{{from 'function_pointer_type_3' (aka 'int * __nonnull (*)(int, int)')}} + fp = (block_type_3)0; // expected-error{{from incompatible type 'block_type_3' (aka 'int * __nonnull (^)(int, int)')}} + fp = (function_pointer_type_4)0; // expected-warning{{from 'function_pointer_type_4' (aka 'int_ptr __nonnull (*)(int, int)')}} + fp = (block_type_4)0; // expected-error{{from incompatible type 'block_type_4' (aka 'int_ptr __nonnull (^)(int, int)')}} + + acceptFunctionPtr(0); // no-warning + acceptBlockPtr(0); // no-warning +} + +// Moving nullability where it creates a conflict. +typedef __nonnull int * __nullable * conflict_int_ptr_ptr_2; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'}} + +// Nullability is not part of the canonical type. +typedef int * __nonnull ambiguous_int_ptr; +typedef int * ambiguous_int_ptr; +typedef int * __nullable ambiguous_int_ptr; + +// Printing of nullability. +float f; +int * __nonnull ip_1 = &f; // expected-warning{{incompatible pointer types initializing 'int * __nonnull' with an expression of type 'float *'}} + +// Check printing of nullability specifiers. +void printing_nullability(void) { + int * __nonnull iptr; + float *fptr = iptr; // expected-warning{{incompatible pointer types initializing 'float *' with an expression of type 'int * __nonnull'}} + + int * * __nonnull iptrptr; + float **fptrptr = iptrptr; // expected-warning{{incompatible pointer types initializing 'float **' with an expression of type 'int ** __nonnull'}} + + int * __nullable * __nonnull iptrptr2; + float * *fptrptr2 = iptrptr2; // expected-warning{{incompatible pointer types initializing 'float **' with an expression of type 'int * __nullable * __nonnull'}} +} diff --git a/test/SemaCXX/nullability-declspec.cpp b/test/SemaCXX/nullability-declspec.cpp new file mode 100644 index 0000000000..ef1a171c4d --- /dev/null +++ b/test/SemaCXX/nullability-declspec.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -fblocks -Werror=nullability-declspec -verify %s + +struct X { }; + +__nullable int *ip1; // expected-error{{nullability specifier '__nullable' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the pointer?}} +__nullable int (*fp1)(int); // expected-error{{nullability specifier '__nullable' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the function pointer?}} +__nonnull int (^bp1)(int); // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the block pointer?}} +__nonnull int X::*pmd1; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the member pointer?}} +__nonnull int (X::*pmf1)(int); // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'; did you mean to apply the specifier to the member function pointer?}} diff --git a/test/SemaCXX/nullability.cpp b/test/SemaCXX/nullability.cpp new file mode 100644 index 0000000000..391675fed7 --- /dev/null +++ b/test/SemaCXX/nullability.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -Wno-nullability-declspec %s -verify + +typedef decltype(nullptr) nullptr_t; + +class X { +}; + +// Nullability applies to all pointer types. +typedef int (X::* __nonnull member_function_type_1)(int); +typedef int X::* __nonnull member_data_type_1; +typedef nullptr_t __nonnull nonnull_nullptr_t; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'nullptr_t'}} + +// Nullability can move into member pointers (this is suppressing a warning). +typedef __nonnull int (X::* member_function_type_2)(int); +typedef int (X::* __nonnull member_function_type_3)(int); +typedef __nonnull int X::* member_data_type_2; + +// Adding non-null via a template. +template +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 'nullptr_t'}} +}; + +typedef AddNonNull::type nonnull_int_ptr_1; +typedef AddNonNull::type nonnull_int_ptr_2; // FIXME: check that it was overridden +typedef AddNonNull::type nonnull_int_ptr_3; // expected-note{{in instantiation of template class}} + +typedef AddNonNull::type nonnull_non_pointer_1; // expected-note{{in instantiation of template class 'AddNonNull' requested here}} + +// Non-null checking within a template. +template +struct AddNonNull2 { + typedef __nonnull AddNonNull invalid1; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull'}} + typedef __nonnull AddNonNull2 invalid2; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull2'}} + typedef __nonnull AddNonNull2 invalid3; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull2'}} + typedef __nonnull typename AddNonNull::type okay1; + + // Don't move past a dependent type even if we know that nullability + // cannot apply to that specific dependent type. + typedef __nonnull AddNonNull (*invalid4); // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull'}} +}; diff --git a/test/SemaObjC/nullability.m b/test/SemaObjC/nullability.m new file mode 100644 index 0000000000..a93072f3dc --- /dev/null +++ b/test/SemaObjC/nullability.m @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -Woverriding-method-mismatch -Wno-nullability-declspec %s -verify + +@interface NSFoo +@end + +// Nullability applies to all pointer types. +typedef NSFoo * __nonnull nonnull_NSFoo_ptr; +typedef id __nonnull nonnull_id; +typedef SEL __nonnull nonnull_SEL; + +// Nullability can move into Objective-C pointer types. +typedef __nonnull NSFoo * nonnull_NSFoo_ptr_2; + +// Conflicts from nullability moving into Objective-C pointer type. +typedef __nonnull NSFoo * __nullable conflict_NSFoo_ptr_2; // expected-error{{'__nonnull' cannot be applied to non-pointer type 'NSFoo'}} + +void testBlocksPrinting(NSFoo * __nullable (^bp)(int)) { + int *ip = bp; // expected-error{{'NSFoo * __nullable (^)(int)'}} +}