From 109dad2dfc7f1f23e826832aee0d77a51fbf4d6c Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Fri, 19 Jun 2015 18:25:57 +0000 Subject: [PATCH] Introduced pragmas for audited nullability regions. Introduce the clang pragmas "assume_nonnull begin" and "assume_nonnull end" in which we make default assumptions about the nullability of many unannotated pointers: - Single-level pointers are inferred to __nonnull - NSError** in a (function or method) parameter list is inferred to NSError * __nullable * __nullable. - CFErrorRef * in a (function or method) parameter list is inferred to CFErrorRef __nullable * __nullable. - Other multi-level pointers are never inferred to anything. Implements rdar://problem/19191042. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@240156 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticLexKinds.td | 17 +- include/clang/Lex/Preprocessor.h | 18 + include/clang/Parse/Parser.h | 13 +- include/clang/Sema/DeclSpec.h | 19 +- include/clang/Sema/Sema.h | 26 ++ lib/Lex/PPDirectives.cpp | 9 + lib/Lex/PPLexerChange.cpp | 11 + lib/Lex/PPMacroExpansion.cpp | 1 + lib/Lex/Pragma.cpp | 55 +++ lib/Parse/ParseObjc.cpp | 55 +-- lib/Sema/SemaExprObjC.cpp | 4 + lib/Sema/SemaObjCProperty.cpp | 17 +- lib/Sema/SemaType.cpp | 411 +++++++++++++++++- test/SemaObjC/arc-property-decl-attrs.m | 22 +- .../SemaObjCXX/Inputs/nullability-pragmas-1.h | 98 +++++ .../SemaObjCXX/Inputs/nullability-pragmas-2.h | 12 + .../SemaObjCXX/Inputs/nullability-pragmas-3.h | 0 test/SemaObjCXX/nullability-pragmas.mm | 41 ++ 18 files changed, 751 insertions(+), 78 deletions(-) create mode 100644 test/SemaObjCXX/Inputs/nullability-pragmas-1.h create mode 100644 test/SemaObjCXX/Inputs/nullability-pragmas-2.h create mode 100644 test/SemaObjCXX/Inputs/nullability-pragmas-3.h create mode 100644 test/SemaObjCXX/nullability-pragmas.mm diff --git a/include/clang/Basic/DiagnosticLexKinds.td b/include/clang/Basic/DiagnosticLexKinds.td index 3d568e84a9..5dd4ae0ac7 100644 --- a/include/clang/Basic/DiagnosticLexKinds.td +++ b/include/clang/Basic/DiagnosticLexKinds.td @@ -646,5 +646,20 @@ def warn_header_guard : Warning< "%0 is used as a header guard here, followed by #define of a different macro">, InGroup>; def note_header_guard : Note< - "%0 is defined here; did you mean %1?">; + "%0 is defined here; did you mean %1?">; + +let CategoryName = "Nullability Issue" in { + +def err_pp_assume_nonnull_syntax : Error<"expected 'begin' or 'end'">; +def err_pp_double_begin_of_assume_nonnull : Error< + "already inside '#pragma clang assume_nonnull'">; +def err_pp_unmatched_end_of_assume_nonnull : Error< + "not currently inside '#pragma clang assume_nonnull'">; +def err_pp_include_in_assume_nonnull : Error< + "cannot #include files inside '#pragma clang assume_nonnull'">; +def err_pp_eof_in_assume_nonnull : Error< + "'#pragma clang assume_nonnull' was not ended within this file">; + +} + } diff --git a/include/clang/Lex/Preprocessor.h b/include/clang/Lex/Preprocessor.h index 2f4714bb1e..439a28041e 100644 --- a/include/clang/Lex/Preprocessor.h +++ b/include/clang/Lex/Preprocessor.h @@ -256,6 +256,10 @@ class Preprocessor : public RefCountedBase { /// \#pragma clang arc_cf_code_audited begin. SourceLocation PragmaARCCFCodeAuditedLoc; + /// \brief The source location of the currently-active + /// \#pragma clang assume_nonnull begin. + SourceLocation PragmaAssumeNonNullLoc; + /// \brief True if we hit the code-completion point. bool CodeCompletionReached; @@ -1250,6 +1254,20 @@ public: PragmaARCCFCodeAuditedLoc = Loc; } + /// \brief The location of the currently-active \#pragma clang + /// assume_nonnull begin. + /// + /// Returns an invalid location if there is no such pragma active. + SourceLocation getPragmaAssumeNonNullLoc() const { + return PragmaAssumeNonNullLoc; + } + + /// \brief Set the location of the currently-active \#pragma clang + /// assume_nonnull begin. An invalid location ends the pragma. + void setPragmaAssumeNonNullLoc(SourceLocation Loc) { + PragmaAssumeNonNullLoc = Loc; + } + /// \brief Set the directory in which the main file should be considered /// to have been found, if it is not a real file. void setMainFileDir(const DirectoryEntry *Dir) { diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index c85160f0a4..1025dd72cf 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -139,11 +139,6 @@ class Parser : public CodeCompletionHandler { // used as type traits. llvm::SmallDenseMap RevertibleTypeTraits; - /// Nullability type specifiers. - IdentifierInfo *Ident___nonnull = nullptr; - IdentifierInfo *Ident___nullable = nullptr; - IdentifierInfo *Ident___null_unspecified = nullptr; - std::unique_ptr AlignHandler; std::unique_ptr GCCVisibilityHandler; std::unique_ptr OptionsHandler; @@ -308,9 +303,11 @@ public: return true; } - /// Retrieve the underscored keyword (__nonnull, __nullable, - /// __null_unspecified) that corresponds to the given nullability kind. - IdentifierInfo *getNullabilityKeyword(NullabilityKind nullability); + /// Retrieve the underscored keyword (__nonnull, __nullable) that corresponds + /// to the given nullability kind. + IdentifierInfo *getNullabilityKeyword(NullabilityKind nullability) { + return Actions.getNullabilityKeyword(nullability); + } private: //===--------------------------------------------------------------------===// diff --git a/include/clang/Sema/DeclSpec.h b/include/clang/Sema/DeclSpec.h index 74332016a9..2ec3286ad7 100644 --- a/include/clang/Sema/DeclSpec.h +++ b/include/clang/Sema/DeclSpec.h @@ -1650,7 +1650,13 @@ private: bool InlineParamsUsed; /// \brief true if the declaration is preceded by \c __extension__. - bool Extension : 1; + unsigned Extension : 1; + + /// Indicates whether this is an Objective-C instance variable. + unsigned ObjCIvar : 1; + + /// Indicates whether this is an Objective-C 'weak' property. + unsigned ObjCWeakProperty : 1; /// \brief If this is the second or subsequent declarator in this declaration, /// the location of the comma before this declarator. @@ -1669,7 +1675,8 @@ public: GroupingParens(false), FunctionDefinition(FDK_Declaration), Redeclaration(false), Attrs(ds.getAttributePool().getFactory()), AsmLabel(nullptr), - InlineParamsUsed(false), Extension(false) { + InlineParamsUsed(false), Extension(false), ObjCIvar(false), + ObjCWeakProperty(false) { } ~Declarator() { @@ -1747,6 +1754,8 @@ public: Attrs.clear(); AsmLabel = nullptr; InlineParamsUsed = false; + ObjCIvar = false; + ObjCWeakProperty = false; CommaLoc = SourceLocation(); EllipsisLoc = SourceLocation(); } @@ -2155,6 +2164,12 @@ public: void setExtension(bool Val = true) { Extension = Val; } bool getExtension() const { return Extension; } + void setObjCIvar(bool Val = true) { ObjCIvar = Val; } + bool isObjCIvar() const { return ObjCIvar; } + + void setObjCWeakProperty(bool Val = true) { ObjCWeakProperty = Val; } + bool isObjCWeakProperty() const { return ObjCWeakProperty; } + void setInvalidType(bool Val = true) { InvalidType = Val; } bool isInvalidType() const { return InvalidType || DS.getTypeSpecType() == DeclSpec::TST_error; diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 981f027239..e54cc7ade3 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -1157,6 +1157,16 @@ public: bool CheckFunctionReturnType(QualType T, SourceLocation Loc); + unsigned deduceWeakPropertyFromType(QualType T) { + if ((getLangOpts().getGC() != LangOptions::NonGC && + T.isObjCGCWeak()) || + (getLangOpts().ObjCAutoRefCount && + T.getObjCLifetime() == Qualifiers::OCL_Weak)) + return ObjCDeclSpec::DQ_PR_weak; + return 0; + } + + /// \brief Build a function type. /// /// This routine checks the function type according to C++ rules and @@ -8782,6 +8792,13 @@ private: mutable IdentifierInfo *Ident_super; mutable IdentifierInfo *Ident___float128; + /// Nullability type specifiers. + IdentifierInfo *Ident___nonnull = nullptr; + IdentifierInfo *Ident___nullable = nullptr; + IdentifierInfo *Ident___null_unspecified = nullptr; + + IdentifierInfo *Ident_NSError = nullptr; + protected: friend class Parser; friend class InitializationSequence; @@ -8790,6 +8807,15 @@ protected: friend class ASTWriter; public: + /// Retrieve the keyword associated + IdentifierInfo *getNullabilityKeyword(NullabilityKind nullability); + + /// The struct behind the CFErrorRef pointer. + RecordDecl *CFError = nullptr; + + /// Retrieve the identifier "NSError". + IdentifierInfo *getNSErrorIdent(); + /// \brief Retrieve the parser's current scope. /// /// This routine must only be used when it is certain that semantic analysis diff --git a/lib/Lex/PPDirectives.cpp b/lib/Lex/PPDirectives.cpp index 43def2b32c..33ce799228 100644 --- a/lib/Lex/PPDirectives.cpp +++ b/lib/Lex/PPDirectives.cpp @@ -1575,6 +1575,15 @@ void Preprocessor::HandleIncludeDirective(SourceLocation HashLoc, PragmaARCCFCodeAuditedLoc = SourceLocation(); } + // Complain about attempts to #include files in an assume-nonnull pragma. + if (PragmaAssumeNonNullLoc.isValid()) { + Diag(HashLoc, diag::err_pp_include_in_assume_nonnull); + Diag(PragmaAssumeNonNullLoc, diag::note_pragma_entered_here); + + // Immediately leave the pragma. + PragmaAssumeNonNullLoc = SourceLocation(); + } + if (HeaderInfo.HasIncludeAliasMap()) { // Map the filename with the brackets still attached. If the name doesn't // map to anything, fall back on the filename we've already gotten the diff --git a/lib/Lex/PPLexerChange.cpp b/lib/Lex/PPLexerChange.cpp index e68fb7df8e..1a35d32ded 100644 --- a/lib/Lex/PPLexerChange.cpp +++ b/lib/Lex/PPLexerChange.cpp @@ -355,6 +355,17 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) { PragmaARCCFCodeAuditedLoc = SourceLocation(); } + // Complain about reaching a true EOF within assume_nonnull. + // We don't want to complain about reaching the end of a macro + // instantiation or a _Pragma. + if (PragmaAssumeNonNullLoc.isValid() && + !isEndOfMacro && !(CurLexer && CurLexer->Is_PragmaLexer)) { + Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull); + + // Recover by leaving immediately. + PragmaAssumeNonNullLoc = SourceLocation(); + } + // If this is a #include'd file, pop it off the include stack and continue // lexing the #includer file. if (!IncludeMacroStack.empty()) { diff --git a/lib/Lex/PPMacroExpansion.cpp b/lib/Lex/PPMacroExpansion.cpp index 62498c6446..ad115bace9 100644 --- a/lib/Lex/PPMacroExpansion.cpp +++ b/lib/Lex/PPMacroExpansion.cpp @@ -1052,6 +1052,7 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) { .Case("address_sanitizer", LangOpts.Sanitize.hasOneOf(SanitizerKind::Address | SanitizerKind::KernelAddress)) + .Case("assume_nonnull", LangOpts.ObjC1 || LangOpts.GNUMode) .Case("attribute_analyzer_noreturn", true) .Case("attribute_availability", true) .Case("attribute_availability_with_message", true) diff --git a/lib/Lex/Pragma.cpp b/lib/Lex/Pragma.cpp index 26ed674f65..5eb665549e 100644 --- a/lib/Lex/Pragma.cpp +++ b/lib/Lex/Pragma.cpp @@ -1342,6 +1342,60 @@ struct PragmaARCCFCodeAuditedHandler : public PragmaHandler { } }; +/// PragmaAssumeNonNullHandler - +/// \#pragma clang assume_nonnull begin/end +struct PragmaAssumeNonNullHandler : public PragmaHandler { + PragmaAssumeNonNullHandler() : PragmaHandler("assume_nonnull") {} + void HandlePragma(Preprocessor &PP, PragmaIntroducerKind Introducer, + Token &NameTok) override { + SourceLocation Loc = NameTok.getLocation(); + bool IsBegin; + + Token Tok; + + // Lex the 'begin' or 'end'. + PP.LexUnexpandedToken(Tok); + const IdentifierInfo *BeginEnd = Tok.getIdentifierInfo(); + if (BeginEnd && BeginEnd->isStr("begin")) { + IsBegin = true; + } else if (BeginEnd && BeginEnd->isStr("end")) { + IsBegin = false; + } else { + PP.Diag(Tok.getLocation(), diag::err_pp_assume_nonnull_syntax); + return; + } + + // Verify that this is followed by EOD. + PP.LexUnexpandedToken(Tok); + if (Tok.isNot(tok::eod)) + PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma"; + + // The start location of the active audit. + SourceLocation BeginLoc = PP.getPragmaAssumeNonNullLoc(); + + // The start location we want after processing this. + SourceLocation NewLoc; + + if (IsBegin) { + // Complain about attempts to re-enter an audit. + if (BeginLoc.isValid()) { + PP.Diag(Loc, diag::err_pp_double_begin_of_assume_nonnull); + PP.Diag(BeginLoc, diag::note_pragma_entered_here); + } + NewLoc = Loc; + } else { + // Complain about attempts to leave an audit that doesn't exist. + if (!BeginLoc.isValid()) { + PP.Diag(Loc, diag::err_pp_unmatched_end_of_assume_nonnull); + return; + } + NewLoc = SourceLocation(); + } + + PP.setPragmaAssumeNonNullLoc(NewLoc); + } +}; + /// \brief Handle "\#pragma region [...]" /// /// The syntax is @@ -1393,6 +1447,7 @@ void Preprocessor::RegisterBuiltinPragmas() { AddPragmaHandler("clang", new PragmaDependencyHandler()); AddPragmaHandler("clang", new PragmaDiagnosticHandler("clang")); AddPragmaHandler("clang", new PragmaARCCFCodeAuditedHandler()); + AddPragmaHandler("clang", new PragmaAssumeNonNullHandler()); AddPragmaHandler("STDC", new PragmaSTDC_FENV_ACCESSHandler()); AddPragmaHandler("STDC", new PragmaSTDC_CX_LIMITED_RANGEHandler()); diff --git a/lib/Parse/ParseObjc.cpp b/lib/Parse/ParseObjc.cpp index 83236602df..a3cf4831e4 100644 --- a/lib/Parse/ParseObjc.cpp +++ b/lib/Parse/ParseObjc.cpp @@ -308,25 +308,6 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc, return ClsType; } -IdentifierInfo *Parser::getNullabilityKeyword(NullabilityKind nullability) { - switch (nullability) { - case NullabilityKind::NonNull: - if (!Ident___nonnull) - Ident___nonnull = PP.getIdentifierInfo("__nonnull"); - return Ident___nonnull; - - case NullabilityKind::Nullable: - if (!Ident___nullable) - Ident___nullable = PP.getIdentifierInfo("__nullable"); - return Ident___nullable; - - case NullabilityKind::Unspecified: - if (!Ident___null_unspecified) - Ident___null_unspecified = PP.getIdentifierInfo("__null_unspecified"); - return Ident___null_unspecified; - } -} - /// Add an attribute for a context-sensitive type nullability to the given /// declarator. static void addContextSensitiveTypeNullability(Parser &P, @@ -1063,31 +1044,28 @@ ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS, SourceLocation loc = ConsumeToken(); Ty = Actions.ActOnObjCInstanceType(loc); + // Synthesize an abstract declarator so we can use Sema::ActOnTypeName. + bool addedToDeclSpec = false; + const char *prevSpec; + unsigned diagID; + DeclSpec declSpec(AttrFactory); + declSpec.setObjCQualifiers(&DS); + declSpec.SetTypeSpecType(DeclSpec::TST_typename, loc, prevSpec, diagID, + Ty, + Actions.getASTContext().getPrintingPolicy()); + declSpec.SetRangeEnd(loc); + Declarator declarator(declSpec, context); + // Map a nullability specifier to a context-sensitive keyword attribute. - if (DS.getObjCDeclQualifier() & ObjCDeclSpec::DQ_CSNullability) { - // Synthesize an abstract declarator so we can use Sema::ActOnTypeName. - bool addedToDeclSpec = false; - const char *prevSpec; - unsigned diagID; - DeclSpec declSpec(AttrFactory); - declSpec.setObjCQualifiers(&DS); - declSpec.SetTypeSpecType(DeclSpec::TST_typename, loc, prevSpec, diagID, - Ty, - Actions.getASTContext().getPrintingPolicy()); - declSpec.SetRangeEnd(loc); - Declarator declarator(declSpec, context); - - // Add the context-sensitive keyword attribute. + if (DS.getObjCDeclQualifier() & ObjCDeclSpec::DQ_CSNullability) addContextSensitiveTypeNullability(*this, declarator, DS.getNullability(), DS.getNullabilityLoc(), addedToDeclSpec); - - TypeResult type = Actions.ActOnTypeName(getCurScope(), declarator); - if (!type.isInvalid()) - Ty = type.get(); - } + TypeResult type = Actions.ActOnTypeName(getCurScope(), declarator); + if (!type.isInvalid()) + Ty = type.get(); } } @@ -1491,6 +1469,7 @@ void Parser::ParseObjCClassInstanceVariables(Decl *interfaceDecl, auto ObjCIvarCallback = [&](ParsingFieldDeclarator &FD) { Actions.ActOnObjCContainerStartDefinition(interfaceDecl); // Install the declarator into the interface decl. + FD.D.setObjCIvar(true); Decl *Field = Actions.ActOnIvar( getCurScope(), FD.D.getDeclSpec().getSourceRange().getBegin(), FD.D, FD.BitfieldSize, visibility); diff --git a/lib/Sema/SemaExprObjC.cpp b/lib/Sema/SemaExprObjC.cpp index 72fc47f126..9947fad70d 100644 --- a/lib/Sema/SemaExprObjC.cpp +++ b/lib/Sema/SemaExprObjC.cpp @@ -1231,6 +1231,10 @@ QualType Sema::getMessageSendResultType(QualType ReceiverType, isClassMessage, isSuperMessage); + // If this is a class message, ignore the nullability of the receiver. + if (isClassMessage) + return resultType; + // Map the nullability of the result into a table index. unsigned receiverNullabilityIdx = 0; if (auto nullability = ReceiverType->getNullability(Context)) diff --git a/lib/Sema/SemaObjCProperty.cpp b/lib/Sema/SemaObjCProperty.cpp index d8e7f64836..87fb5b6913 100644 --- a/lib/Sema/SemaObjCProperty.cpp +++ b/lib/Sema/SemaObjCProperty.cpp @@ -103,15 +103,6 @@ static void checkARCPropertyDecl(Sema &S, ObjCPropertyDecl *property) { << propertyLifetime; } -static unsigned deduceWeakPropertyFromType(Sema &S, QualType T) { - if ((S.getLangOpts().getGC() != LangOptions::NonGC && - T.isObjCGCWeak()) || - (S.getLangOpts().ObjCAutoRefCount && - T.getObjCLifetime() == Qualifiers::OCL_Weak)) - return ObjCDeclSpec::DQ_PR_weak; - return 0; -} - /// \brief Check this Objective-C property against a property declared in the /// given protocol. static void @@ -146,9 +137,10 @@ Decl *Sema::ActOnProperty(Scope *S, SourceLocation AtLoc, tok::ObjCKeywordKind MethodImplKind, DeclContext *lexicalDC) { unsigned Attributes = ODS.getPropertyAttributes(); + FD.D.setObjCWeakProperty((Attributes & ObjCDeclSpec::DQ_PR_weak) != 0); TypeSourceInfo *TSI = GetTypeForDeclarator(FD.D, S); QualType T = TSI->getType(); - Attributes |= deduceWeakPropertyFromType(*this, T); + Attributes |= deduceWeakPropertyFromType(T); bool isReadWrite = ((Attributes & ObjCDeclSpec::DQ_PR_readwrite) || // default is readwrite! !(Attributes & ObjCDeclSpec::DQ_PR_readonly)); @@ -433,7 +425,7 @@ Sema::HandlePropertyInClassExtension(Scope *S, if (isReadWrite && (PIkind & ObjCPropertyDecl::OBJC_PR_readonly)) { PIkind &= ~ObjCPropertyDecl::OBJC_PR_readonly; PIkind |= ObjCPropertyDecl::OBJC_PR_readwrite; - PIkind |= deduceWeakPropertyFromType(*this, PIDecl->getType()); + PIkind |= deduceWeakPropertyFromType(PIDecl->getType()); unsigned ClassExtensionMemoryModel = getOwnershipRule(Attributes); unsigned PrimaryClassMemoryModel = getOwnershipRule(PIkind); if (PrimaryClassMemoryModel && ClassExtensionMemoryModel && @@ -2293,8 +2285,7 @@ void Sema::CheckObjCPropertyAttributes(Decl *PDecl, Attributes &= ~ObjCDeclSpec::DQ_PR_weak; } - if ((Attributes & ObjCDeclSpec::DQ_PR_weak) && - !(Attributes & ObjCDeclSpec::DQ_PR_readonly)) { + if (Attributes & ObjCDeclSpec::DQ_PR_weak) { // 'weak' and 'nonnull' are mutually exclusive. if (auto nullability = PropertyTy->getNullability(Context)) { if (*nullability == NullabilityKind::NonNull) diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 750172c4cd..979491af5c 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -25,6 +25,7 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Basic/PartialDiagnostic.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Parse/ParseDiagnostic.h" #include "clang/Sema/DeclSpec.h" #include "clang/Sema/DelayedDiagnostic.h" @@ -2553,6 +2554,211 @@ getCCForDeclaratorChunk(Sema &S, Declarator &D, return CC; } +namespace { + /// A simple notion of pointer kinds, which matches up with the various + /// pointer declarators. + enum class SimplePointerKind { + Pointer, + BlockPointer, + MemberPointer, + }; +} + +IdentifierInfo *Sema::getNullabilityKeyword(NullabilityKind nullability) { + switch (nullability) { + case NullabilityKind::NonNull: + if (!Ident___nonnull) + Ident___nonnull = PP.getIdentifierInfo("__nonnull"); + return Ident___nonnull; + + case NullabilityKind::Nullable: + if (!Ident___nullable) + Ident___nullable = PP.getIdentifierInfo("__nullable"); + return Ident___nullable; + + case NullabilityKind::Unspecified: + if (!Ident___null_unspecified) + Ident___null_unspecified = PP.getIdentifierInfo("__null_unspecified"); + return Ident___null_unspecified; + } +} + +/// Retrieve the identifier "NSError". +IdentifierInfo *Sema::getNSErrorIdent() { + if (!Ident_NSError) + Ident_NSError = PP.getIdentifierInfo("NSError"); + + return Ident_NSError; +} + +/// 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; +} + +namespace { + /// Describes the kind of a pointer a declarator describes. + enum class PointerDeclaratorKind { + // Not a pointer. + NonPointer, + // Single-level pointer. + SingleLevelPointer, + // Multi-level pointer (of any pointer kind). + MultiLevelPointer, + // CFErrorRef* + CFErrorRefPointer, + // NSError** + NSErrorPointerPointer, + }; +} + +/// Classify the given declarator, whose type-specified is \c type, based on +/// what kind of pointer it refers to. +/// +/// This is used to determine the default nullability. +static PointerDeclaratorKind classifyPointerDeclarator(Sema &S, + QualType type, + Declarator &declarator) { + unsigned numNormalPointers = 0; + + // For any dependent type, we consider it a non-pointer. + if (type->isDependentType()) + return PointerDeclaratorKind::NonPointer; + + // Look through the declarator chunks to identify pointers. + for (unsigned i = 0, n = declarator.getNumTypeObjects(); i != n; ++i) { + DeclaratorChunk &chunk = declarator.getTypeObject(i); + switch (chunk.Kind) { + case DeclaratorChunk::Array: + case DeclaratorChunk::Function: + break; + + case DeclaratorChunk::BlockPointer: + case DeclaratorChunk::MemberPointer: + return numNormalPointers > 0 ? PointerDeclaratorKind::MultiLevelPointer + : PointerDeclaratorKind::SingleLevelPointer; + + case DeclaratorChunk::Paren: + case DeclaratorChunk::Reference: + continue; + + case DeclaratorChunk::Pointer: + ++numNormalPointers; + if (numNormalPointers > 2) + return PointerDeclaratorKind::MultiLevelPointer; + continue; + } + } + + // Then, dig into the type specifier itself. + unsigned numTypeSpecifierPointers = 0; + do { + // Decompose normal pointers. + if (auto ptrType = type->getAs()) { + ++numNormalPointers; + + if (numNormalPointers > 2) + return PointerDeclaratorKind::MultiLevelPointer; + + type = ptrType->getPointeeType(); + ++numTypeSpecifierPointers; + continue; + } + + // Decompose block pointers. + if (type->getAs()) { + return numNormalPointers > 0 ? PointerDeclaratorKind::MultiLevelPointer + : PointerDeclaratorKind::SingleLevelPointer; + } + + // Decompose member pointers. + if (type->getAs()) { + return numNormalPointers > 0 ? PointerDeclaratorKind::MultiLevelPointer + : PointerDeclaratorKind::SingleLevelPointer; + } + + // Look at Objective-C object pointers. + if (auto objcObjectPtr = type->getAs()) { + ++numNormalPointers; + ++numTypeSpecifierPointers; + + // If this is NSError**, report that. + if (auto objcClassDecl = objcObjectPtr->getInterfaceDecl()) { + if (objcClassDecl->getIdentifier() == S.getNSErrorIdent() && + numNormalPointers == 2 && numTypeSpecifierPointers < 2) { + return PointerDeclaratorKind::NSErrorPointerPointer; + } + } + + break; + } + + // Look at Objective-C class types. + if (auto objcClass = type->getAs()) { + if (objcClass->getInterface()->getIdentifier() == S.getNSErrorIdent()) { + if (numNormalPointers == 2 && numTypeSpecifierPointers < 2) + return PointerDeclaratorKind::NSErrorPointerPointer;; + } + + break; + } + + // If at this point we haven't seen a pointer, we won't see one. + if (numNormalPointers == 0) + return PointerDeclaratorKind::NonPointer; + + if (auto recordType = type->getAs()) { + RecordDecl *recordDecl = recordType->getDecl(); + + bool isCFError = false; + if (S.CFError) { + // If we already know about CFError, test it directly. + isCFError = (S.CFError == recordDecl); + } else { + // Check whether this is CFError, which we identify based on its bridge + // to NSError. + if (recordDecl->getTagKind() == TTK_Struct && numNormalPointers > 0) { + if (auto bridgeAttr = recordDecl->getAttr()) { + if (bridgeAttr->getBridgedType() == S.getNSErrorIdent()) { + S.CFError = recordDecl; + isCFError = true; + } + } + } + } + + // If this is CFErrorRef*, report it as such. + if (isCFError && numNormalPointers == 2 && numTypeSpecifierPointers < 2) { + return PointerDeclaratorKind::CFErrorRefPointer; + } + break; + } + + break; + } while (true); + + + switch (numNormalPointers) { + case 0: + return PointerDeclaratorKind::NonPointer; + + case 1: + return PointerDeclaratorKind::SingleLevelPointer; + + default: + return PointerDeclaratorKind::MultiLevelPointer; + } +} + static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, QualType declSpecType, TypeSourceInfo *TInfo) { @@ -2620,6 +2826,183 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, } } + // Determine whether we should infer __nonnull on pointer types. + Optional inferNullability; + bool inferNullabilityCS = false; + + // Are we in an assume-nonnull region? + bool inAssumeNonNullRegion = false; + if (S.PP.getPragmaAssumeNonNullLoc().isValid() && + !state.getDeclarator().isObjCWeakProperty() && + !S.deduceWeakPropertyFromType(T)) { + inAssumeNonNullRegion = true; + } + + // Whether to complain about missing nullability specifiers or not. + enum { + /// Never complain. + CAMN_No, + /// Complain on the inner pointers (but not the outermost + /// pointer). + CAMN_InnerPointers, + /// Complain about any pointers that don't have nullability + /// specified or inferred. + CAMN_Yes + } complainAboutMissingNullability = CAMN_No; + unsigned NumPointersRemaining = 0; + + if (IsTypedefName) { + // For typedefs, we do not infer any nullability (the default), + // and we only complain about missing nullability specifiers on + // inner pointers. + complainAboutMissingNullability = CAMN_InnerPointers; + + if (T->canHaveNullability()) { + ++NumPointersRemaining; + } + + for (unsigned i = 0, n = D.getNumTypeObjects(); i != n; ++i) { + DeclaratorChunk &chunk = D.getTypeObject(i); + switch (chunk.Kind) { + case DeclaratorChunk::Array: + case DeclaratorChunk::Function: + break; + + case DeclaratorChunk::BlockPointer: + case DeclaratorChunk::MemberPointer: + ++NumPointersRemaining; + break; + + case DeclaratorChunk::Paren: + case DeclaratorChunk::Reference: + continue; + + case DeclaratorChunk::Pointer: + ++NumPointersRemaining; + continue; + } + } + } else { + bool isFunctionOrMethod = false; + switch (auto context = state.getDeclarator().getContext()) { + case Declarator::ObjCParameterContext: + case Declarator::ObjCResultContext: + case Declarator::PrototypeContext: + case Declarator::TrailingReturnContext: + isFunctionOrMethod = true; + // fallthrough + + case Declarator::MemberContext: + if (state.getDeclarator().isObjCIvar() && !isFunctionOrMethod) { + complainAboutMissingNullability = CAMN_No; + break; + } + // fallthrough + + case Declarator::FileContext: + case Declarator::KNRTypeListContext: + complainAboutMissingNullability = CAMN_Yes; + + // Nullability inference depends on the type and declarator. + switch (classifyPointerDeclarator(S, T, D)) { + case PointerDeclaratorKind::NonPointer: + case PointerDeclaratorKind::MultiLevelPointer: + // Cannot infer nullability. + break; + + case PointerDeclaratorKind::SingleLevelPointer: + // Infer __nonnull if we are in an assumes-nonnull region. + if (inAssumeNonNullRegion) { + inferNullability = NullabilityKind::NonNull; + inferNullabilityCS = (context == Declarator::ObjCParameterContext || + context == Declarator::ObjCResultContext); + } + break; + + case PointerDeclaratorKind::CFErrorRefPointer: + case PointerDeclaratorKind::NSErrorPointerPointer: + // Within a function or method signature, infer __nullable at both + // levels. + if (isFunctionOrMethod && inAssumeNonNullRegion) + inferNullability = NullabilityKind::Nullable; + break; + } + break; + + case Declarator::ConversionIdContext: + complainAboutMissingNullability = CAMN_Yes; + break; + + case Declarator::AliasDeclContext: + case Declarator::AliasTemplateContext: + case Declarator::BlockContext: + case Declarator::BlockLiteralContext: + case Declarator::ConditionContext: + case Declarator::CXXCatchContext: + case Declarator::CXXNewContext: + case Declarator::ForContext: + case Declarator::LambdaExprContext: + case Declarator::LambdaExprParameterContext: + case Declarator::ObjCCatchContext: + case Declarator::TemplateParamContext: + case Declarator::TemplateTypeArgContext: + case Declarator::TypeNameContext: + // Don't infer in these contexts. + break; + } + } + + // Local function that checks the nullability for a given pointer declarator. + // Returns true if __nonnull was inferred. + auto inferPointerNullability = [&](SimplePointerKind pointerKind, + SourceLocation pointerLoc, + AttributeList *&attrs) -> AttributeList * { + // We've seen a pointer. + if (NumPointersRemaining > 0) + --NumPointersRemaining; + + // If a nullability attribute is present, there's nothing to do. + if (hasNullabilityAttr(attrs)) + return nullptr; + + // If we're supposed to infer nullability, do so now. + if (inferNullability) { + AttributeList *nullabilityAttr = state.getDeclarator().getAttributePool() + .create( + S.getNullabilityKeyword( + *inferNullability), + SourceRange(pointerLoc), + nullptr, SourceLocation(), + nullptr, 0, + AttributeList::AS_Keyword); + if (inferNullabilityCS) + nullabilityAttr->setContextSensitiveKeywordAttribute(); + + spliceAttrIntoList(*nullabilityAttr, attrs); + return nullabilityAttr; + } + + return nullptr; + }; + + // If the type itself could have nullability but does not, infer pointer + // nullability. + if (T->canHaveNullability() && S.ActiveTemplateInstantiations.empty()) { + SimplePointerKind pointerKind = SimplePointerKind::Pointer; + if (T->isBlockPointerType()) + pointerKind = SimplePointerKind::BlockPointer; + else if (T->isMemberPointerType()) + pointerKind = SimplePointerKind::MemberPointer; + + if (auto *attr = inferPointerNullability( + pointerKind, D.getDeclSpec().getTypeSpecTypeLoc(), + D.getMutableDeclSpec().getAttributes().getListRef())) { + T = Context.getAttributedType( + AttributedType::getNullabilityAttrKind(*inferNullability), T, T); + attr->setUsedAsTypeAttr(); + } + } + // Walk the DeclTypeInfo, building the recursive type as we go. // DeclTypeInfos are ordered from the identifier out, which is // opposite of what we want :). @@ -2637,6 +3020,10 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, if (!LangOpts.Blocks) S.Diag(DeclType.Loc, diag::err_blocks_disable); + // Handle pointer nullability. + inferPointerNullability(SimplePointerKind::BlockPointer, + DeclType.Loc, DeclType.getAttrListRef()); + T = S.BuildBlockPointerType(T, D.getIdentifierLoc(), Name); if (DeclType.Cls.TypeQuals) T = S.BuildQualifiedType(T, DeclType.Loc, DeclType.Cls.TypeQuals); @@ -2649,6 +3036,11 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, D.setInvalidType(true); // Build the type anyway. } + + // Handle pointer nullability + inferPointerNullability(SimplePointerKind::Pointer, DeclType.Loc, + DeclType.getAttrListRef()); + if (LangOpts.ObjC1 && T->getAs()) { T = Context.getObjCObjectPointerType(T); if (DeclType.Ptr.TypeQuals) @@ -3090,6 +3482,11 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, // The scope spec must refer to a class, or be dependent. CXXScopeSpec &SS = DeclType.Mem.Scope(); QualType ClsType; + + // Handle pointer nullability. + inferPointerNullability(SimplePointerKind::MemberPointer, + DeclType.Loc, DeclType.getAttrListRef()); + if (SS.isInvalid()) { // Avoid emitting extra errors if we already errored on the scope. D.setInvalidType(true); @@ -4614,20 +5011,6 @@ bool Sema::checkNullabilityTypeSpecifier(QualType &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; -} - /// Map a nullability attribute kind to a nullability kind. static NullabilityKind mapNullabilityAttrKind(AttributeList::Kind kind) { switch (kind) { diff --git a/test/SemaObjC/arc-property-decl-attrs.m b/test/SemaObjC/arc-property-decl-attrs.m index e5b8f28e5a..2d46917772 100644 --- a/test/SemaObjC/arc-property-decl-attrs.m +++ b/test/SemaObjC/arc-property-decl-attrs.m @@ -80,10 +80,28 @@ @end // rdar://20152386 +// rdar://20383235 + @interface NSObject @end -@interface rdar20152386_2: NSObject +#pragma clang assume_nonnull begin +@interface I: NSObject +@property(nonatomic, weak) id delegate; // Do not warn, nullable is inferred. +@property(nonatomic, weak, readonly) id ROdelegate; // Do not warn, nullable is inferred. +@property(nonatomic, weak, nonnull) id NonNulldelete; // expected-error {{property attributes 'nonnull' and 'weak' are mutually exclusive}} +@property(nonatomic, weak, nullable) id Nullabledelete; // do not warn + +// strong cases. +@property(nonatomic, strong) id stdelegate; // Do not warn +@property(nonatomic, readonly) id stROdelegate; // Do not warn +@property(nonatomic, strong, nonnull) id stNonNulldelete; // Do not warn +@property(nonatomic, nullable) id stNullabledelete; // do not warn +@end +#pragma clang assume_nonnull end + +@interface J: NSObject +@property(nonatomic, weak) id ddd; // Do not warn, nullable is inferred. @property(nonatomic, weak, nonnull) id delegate; // expected-error {{property attributes 'nonnull' and 'weak' are mutually exclusive}} -@property(nonatomic, weak, nonnull, readonly) id ReadDelegate; // no warning +@property(nonatomic, weak, nonnull, readonly) id ROdelegate; // expected-error {{property attributes 'nonnull' and 'weak' are mutually exclusive}} @end diff --git a/test/SemaObjCXX/Inputs/nullability-pragmas-1.h b/test/SemaObjCXX/Inputs/nullability-pragmas-1.h new file mode 100644 index 0000000000..76f9af4e4e --- /dev/null +++ b/test/SemaObjCXX/Inputs/nullability-pragmas-1.h @@ -0,0 +1,98 @@ +__attribute__((objc_root_class)) +@interface NSError +@end + +__attribute__((objc_root_class)) +@interface A +@end + +struct X { }; + +void f1(int *x); + +typedef struct __attribute__((objc_bridge(NSError))) __CFError *CFErrorRef; +typedef NSError *NSErrorPtr; +typedef NSError **NSErrorPtrPtr; +typedef CFErrorRef *CFErrorRefPtr; +typedef int *int_ptr; +typedef A *A_ptr; +typedef int (^block_ptr)(int, int); + +#pragma clang assume_nonnull begin + +void f2(int *x); +void f3(A* obj); +void f4(int (^block)(int, int)); +void f5(int_ptr x); +void f6(A_ptr obj); +void f7(int * __nullable x); +void f8(A * __nullable obj); +void f9(int X::* mem_ptr); +void f10(int (X::*mem_func)(int, int)); +void f11(int X::* __nullable mem_ptr); +void f12(int (X::* __nullable mem_func)(int, int)); + +int_ptr f13(void); +A *f14(void); + +int * __null_unspecified f15(void); +A * __null_unspecified f16(void); +void f17(CFErrorRef *error); // expected-note{{no known conversion from 'A * __nonnull' to 'CFErrorRef __nullable * __nullable' (aka '__CFError **') for 1st argument}} +void f18(A **); +void f19(CFErrorRefPtr error); + +void g1(int (^)(int, int)); +void g2(int (^ *bp)(int, int)); +void g3(block_ptr *bp); +void g4(int (*fp)(int, int)); +void g5(int (**fp)(int, int)); + +@interface A(Pragmas1) ++ (instancetype)aWithA:(A *)a; +- (A *)method1:(A_ptr)ptr; +- (null_unspecified A *)method2; +- (void)method3:(NSError **)error; // expected-note{{passing argument to parameter 'error' here}} +- (void)method4:(NSErrorPtr *)error; // expected-note{{passing argument to parameter 'error' here}} +- (void)method5:(NSErrorPtrPtr)error; + +@property A *aProp; +@property NSError **anError; +@end + +int *global_int_ptr; + +// typedefs not inferred __nonnull +typedef int *int_ptr_2; + +typedef int * + *int_ptr_ptr; + +static inline void f30(void) { + float *fp = global_int_ptr; // expected-error{{cannot initialize a variable of type 'float *' with an lvalue of type 'int * __nonnull'}} + + int_ptr_2 ip2; + float *fp2 = ip2; // expected-error{{cannot initialize a variable of type 'float *' with an lvalue of type 'int_ptr_2' (aka 'int *')}} + + int_ptr_ptr ipp; + float *fp3 = ipp; // expected-error{{lvalue of type 'int_ptr_ptr' (aka 'int **')}} +} + +@interface AA : A { +@public + id ivar1; + __nonnull id ivar2; +} +@end + +#pragma clang assume_nonnull end + +void f20(A *a); +void f21(int_ptr x); +void f22(A_ptr y); +void f23(int_ptr __nullable x); +void f24(A_ptr __nullable y); +void f25(int_ptr_2 x); + +@interface A(OutsidePragmas1) ++ (instancetype)aWithInt:(int)value; +@end diff --git a/test/SemaObjCXX/Inputs/nullability-pragmas-2.h b/test/SemaObjCXX/Inputs/nullability-pragmas-2.h new file mode 100644 index 0000000000..da3d21de69 --- /dev/null +++ b/test/SemaObjCXX/Inputs/nullability-pragmas-2.h @@ -0,0 +1,12 @@ +#pragma clang assume_nonnull start // expected-error{{expected 'begin' or 'end'}} + +#pragma clang assume_nonnull begin // expected-note{{#pragma entered here}} + +#include "nullability-pragmas-3.h" // expected-error{{cannot #include files inside '#pragma clang assume_nonnull'}} + +#pragma clang assume_nonnull begin // expected-note{{#pragma entered here}} +#pragma clang assume_nonnull begin // expected-error{{already inside '#pragma clang assume_nonnull'}} +#pragma clang assume_nonnull end + +#pragma clang assume_nonnull begin // expected-error{{'#pragma clang assume_nonnull' was not ended within this file}} + diff --git a/test/SemaObjCXX/Inputs/nullability-pragmas-3.h b/test/SemaObjCXX/Inputs/nullability-pragmas-3.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/SemaObjCXX/nullability-pragmas.mm b/test/SemaObjCXX/nullability-pragmas.mm new file mode 100644 index 0000000000..0c61a30b33 --- /dev/null +++ b/test/SemaObjCXX/nullability-pragmas.mm @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -I %S/Inputs %s -verify + +#include "nullability-pragmas-1.h" +#include "nullability-pragmas-2.h" + +#if !__has_feature(assume_nonnull) +# error assume_nonnull feature is not set +#endif + +void test_pragmas_1(A * __nonnull a, AA * __nonnull aa) { + f1(0); // okay: no nullability annotations + f2(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f3(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f4(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f5(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f6(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f7(0); // okay + f8(0); // okay + f9(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f10(0); // expected-warning{{null passed to a callee that requires a non-null argument}} + f11(0); // okay + f12(0); // okay + [a method1:0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + + f17(a); // expected-error{{no matching function for call to 'f17'}} + [a method3: a]; // expected-error{{cannot initialize a parameter of type 'NSError * __nullable * __nullable' with an lvalue of type 'A * __nonnull'}} + [a method4: a]; // expected-error{{cannot initialize a parameter of type 'NSErrorPtr __nullable * __nullable' (aka 'NSError **') with an lvalue of type 'A * __nonnull'}} + + float *ptr; + ptr = f13(); // expected-error{{assigning to 'float *' from incompatible type 'int_ptr __nonnull' (aka 'int *')}} + ptr = f14(); // expected-error{{assigning to 'float *' from incompatible type 'A * __nonnull'}} + ptr = [a method1:a]; // expected-error{{assigning to 'float *' from incompatible type 'A * __nonnull'}} + ptr = a.aProp; // expected-error{{assigning to 'float *' from incompatible type 'A * __nonnull'}} + ptr = global_int_ptr; // expected-error{{assigning to 'float *' from incompatible type 'int * __nonnull'}} + ptr = f15(); // expected-error{{assigning to 'float *' from incompatible type 'int * __null_unspecified'}} + ptr = f16(); // expected-error{{assigning to 'float *' from incompatible type 'A * __null_unspecified'}} + ptr = [a method2]; // expected-error{{assigning to 'float *' from incompatible type 'A * __null_unspecified'}} + + ptr = aa->ivar1; // expected-error{{from incompatible type 'id'}} + ptr = aa->ivar2; // expected-error{{from incompatible type 'id __nonnull'}} +} -- 2.40.0