diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h index 8cd29b7b917eafe5898100fff9ef6770d6bd1370..d821258736237686496635c76ec7a4b581755389 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<NullabilityKind> 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<NullabilityKind> 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<NullabilityKind> 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 8394655c4b665c3ff6989b0d5448164c98e0e3b7..f36f654ff6301daa39132f63788dd5113bd210c4 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 deb23f3149cba97f149b5de6f49b63c75db7192e..853259434245de0080bf6562cb9166c8c6ffc214 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<Nullability>; + +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 13dcc73dd1330c9809dfa86b48d6e9c2fb0aa8bd..84ffe09bbe62c5c026ad9578098db9f36f16f052 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 3df8b949a1611d37738d218089b3b6f9eb0e074d..6a11d240c63ce3f7e6d74d622004fead12897fd5 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<KeywordCompat>; +def ext_nullability : Extension< + "type nullability specifier %0 is a Clang extension">, + InGroup<DiagGroup<"nullability-extension">>; + 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 b8fadda8547049bf2a1a3883a1d6073f224ac9a2..ac534436e761d1e7d4d53876190496c0be27358b 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<Nullability>; + +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<NullabilityDeclSpec>, + 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 7569c16412b222a469af36b838a30f1cf00ab2bb..2a872b277ea2ed69c578dc0cd24589dd354e7bde 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 b6d983b552452d90062b8e92a820136ecc6349c9..67b9933562ebb357c5df57f80d6fa67a22d2f8ea 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 caba77b74c673eeeb1e8954f88824c463acf9a73..3d48f0ad5c930028369a581645b1f67f31c8d2bb 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 09bb7692596c696d0315fa68c0f5dd611a26b99a..15f3d39634d3ab850dff0e1ae97f3137c66c9a31 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<NullabilityKind> 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<AttributedType>(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<TemplateSpecializationType>(type.getTypePtr()) + ->getTemplateName().getAsTemplateDecl()) { + if (isa<ClassTemplateDecl>(templateDecl)) + return false; + } + return true; + + // auto is considered dependent when it isn't deduced. + case Type::Auto: + return !cast<AutoType>(type.getTypePtr())->isDeduced(); + + case Type::Builtin: + switch (cast<BuiltinType>(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<NullabilityKind> 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<NullabilityKind> AttributedType::stripOuterNullability(QualType &T) { + if (auto attributed = dyn_cast<AttributedType>(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 3928fe8f89420705b5b57964b3da78360ff715f0..ebe09d85495e15dc25fba8468d2a7696831a474d 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<bool> 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 ccfb30240b0bf8b571bb31ebd9901abcccf0be2e..62498c6446473a931b331113d74aec7a46022737 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<bool>(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 eb6800d65c9e51e3ed4074fc0622d7599cae275e..96555fcf8773b8a41f54193e5430d37f16480cf3 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 e21409a62fcba0ac0ac9b722a96b08c6bbd9058e..d63cf24bcdb997940949ce98d9879a85b1563d7f 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 73bdd984c0beb516ed43ed0c501f259eac10a5a1..f4af3f320a8ee1107cee660dd28bad3e0b06c11a 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<unsigned>(*Newnullability) + << static_cast<unsigned>(*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 d3787ec44b12da4b1a59a66559804076e88aaf91..ea749a46e09b8b854c0cd81a2e32ca2458cbd6ea 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<AttributedType>(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<unsigned>(nullability); + return true; + } + + // Conflicting nullability. + S.Diag(attr.getLoc(), diag::err_nullability_conflicting) + << static_cast<unsigned>(nullability) + << static_cast<unsigned>(*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<unsigned>(nullability) + << static_cast<unsigned>(*existingNullability); + + // Try to find the typedef with the existing nullability specifier. + if (auto typedefType = desugared->getAs<TypedefType>()) { + 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<unsigned>(*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<unsigned>(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<unsigned>(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 878300ebc9f81208f1ad15ff8153bdd95bf34ba0..c4cd22609b7a3d6ffc58b54779aea3cc6034a216 100644 --- a/lib/Sema/TreeTransform.h +++ b/lib/Sema/TreeTransform.h @@ -5386,6 +5386,17 @@ QualType TreeTransform<Derived>::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<unsigned>(*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 0000000000000000000000000000000000000000..2ac20b9d9b4c83f9f4d2040b2f26cdc79100113b --- /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 0000000000000000000000000000000000000000..f2b6abf73dcf69f63a4748e6f301110bd918b3be --- /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 0000000000000000000000000000000000000000..0cabc713bafc8186b2285a8fa6b7bb1e6bfc75b7 --- /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 0000000000000000000000000000000000000000..bf35b4ca1a61587c8b3c6120783e5970b84a8034 --- /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 0000000000000000000000000000000000000000..ef1a171c4ddb1ce2f1775f7b7ce8341310b677ab --- /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 0000000000000000000000000000000000000000..391675fed77b9df2b56f82e706507a72649537a6 --- /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<typename T> +struct AddNonNull { + typedef __nonnull T type; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'int'}} + // expected-error@-1{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'nullptr_t'}} +}; + +typedef AddNonNull<int *>::type nonnull_int_ptr_1; +typedef AddNonNull<int * __nullable>::type nonnull_int_ptr_2; // FIXME: check that it was overridden +typedef AddNonNull<nullptr_t>::type nonnull_int_ptr_3; // expected-note{{in instantiation of template class}} + +typedef AddNonNull<int>::type nonnull_non_pointer_1; // expected-note{{in instantiation of template class 'AddNonNull<int>' requested here}} + +// Non-null checking within a template. +template<typename T> +struct AddNonNull2 { + typedef __nonnull AddNonNull<T> invalid1; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull<T>'}} + typedef __nonnull AddNonNull2 invalid2; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull2<T>'}} + typedef __nonnull AddNonNull2<T> invalid3; // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull2<T>'}} + typedef __nonnull typename AddNonNull<T>::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<T> (*invalid4); // expected-error{{nullability specifier '__nonnull' cannot be applied to non-pointer type 'AddNonNull<T>'}} +}; diff --git a/test/SemaObjC/nullability.m b/test/SemaObjC/nullability.m new file mode 100644 index 0000000000000000000000000000000000000000..a93072f3dc600b98c5f94082ee444d46f782a997 --- /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)'}} +}