diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4424,6 +4424,8 @@ /// Issue any -Wunguarded-availability warnings in \c FD void DiagnoseUnguardedAvailabilityViolations(Decl *FD); + void handleDelayedAvailabilityCheck(sema::DelayedDiagnostic &DD, Decl *Ctx); + //===--------------------------------------------------------------------===// // Expression Parsing Callbacks: SemaExpr.cpp. diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -29,6 +29,7 @@ Sema.cpp SemaAccess.cpp SemaAttr.cpp + SemaAvailability.cpp SemaCXXScopeSpec.cpp SemaCast.cpp SemaChecking.cpp diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Sema/SemaAvailability.cpp @@ -0,0 +1,963 @@ +//===--- SemaAvailability.cpp - Availability attribute handling -----------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file processes the availability attribute. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/DiagnosticSema.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Sema/DelayedDiagnostic.h" +#include "clang/Sema/ScopeInfo.h" +#include "clang/Sema/Sema.h" + +using namespace clang; +using namespace sema; + +static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context, + const Decl *D) { + // Check each AvailabilityAttr to find the one for this platform. + for (const auto *A : D->attrs()) { + if (const auto *Avail = dyn_cast(A)) { + // FIXME: this is copied from CheckAvailability. We should try to + // de-duplicate. + + // Check if this is an App Extension "platform", and if so chop off + // the suffix for matching with the actual platform. + StringRef ActualPlatform = Avail->getPlatform()->getName(); + StringRef RealizedPlatform = ActualPlatform; + if (Context.getLangOpts().AppExt) { + size_t suffix = RealizedPlatform.rfind("_app_extension"); + if (suffix != StringRef::npos) + RealizedPlatform = RealizedPlatform.slice(0, suffix); + } + + StringRef TargetPlatform = Context.getTargetInfo().getPlatformName(); + + // Match the platform name. + if (RealizedPlatform == TargetPlatform) + return Avail; + } + } + return nullptr; +} + +/// The diagnostic we should emit for \c D, and the declaration that +/// originated it, or \c AR_Available. +/// +/// \param D The declaration to check. +/// \param Message If non-null, this will be populated with the message from +/// the availability attribute that is selected. +/// \param ClassReceiver If we're checking the the method of a class message +/// send, the class. Otherwise nullptr. +static std::pair +ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D, + std::string *Message, + ObjCInterfaceDecl *ClassReceiver) { + AvailabilityResult Result = D->getAvailability(Message); + + // For typedefs, if the typedef declaration appears available look + // to the underlying type to see if it is more restrictive. + while (const auto *TD = dyn_cast(D)) { + if (Result == AR_Available) { + if (const auto *TT = TD->getUnderlyingType()->getAs()) { + D = TT->getDecl(); + Result = D->getAvailability(Message); + continue; + } + } + break; + } + + // Forward class declarations get their attributes from their definition. + if (const auto *IDecl = dyn_cast(D)) { + if (IDecl->getDefinition()) { + D = IDecl->getDefinition(); + Result = D->getAvailability(Message); + } + } + + if (const auto *ECD = dyn_cast(D)) + if (Result == AR_Available) { + const DeclContext *DC = ECD->getDeclContext(); + if (const auto *TheEnumDecl = dyn_cast(DC)) { + Result = TheEnumDecl->getAvailability(Message); + D = TheEnumDecl; + } + } + + // For +new, infer availability from -init. + if (const auto *MD = dyn_cast(D)) { + if (S.NSAPIObj && ClassReceiver) { + ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod( + S.NSAPIObj->getInitSelector()); + if (Init && Result == AR_Available && MD->isClassMethod() && + MD->getSelector() == S.NSAPIObj->getNewSelector() && + MD->definedInNSObject(S.getASTContext())) { + Result = Init->getAvailability(Message); + D = Init; + } + } + } + + return {Result, D}; +} + + +/// whether we should emit a diagnostic for \c K and \c DeclVersion in +/// the context of \c Ctx. For example, we should emit an unavailable diagnostic +/// in a deprecated context, but not the other way around. +static bool +ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K, + VersionTuple DeclVersion, Decl *Ctx, + const NamedDecl *OffendingDecl) { + assert(K != AR_Available && "Expected an unavailable declaration here!"); + + // Checks if we should emit the availability diagnostic in the context of C. + auto CheckContext = [&](const Decl *C) { + if (K == AR_NotYetIntroduced) { + if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C)) + if (AA->getIntroduced() >= DeclVersion) + return true; + } else if (K == AR_Deprecated) { + if (C->isDeprecated()) + return true; + } else if (K == AR_Unavailable) { + // It is perfectly fine to refer to an 'unavailable' Objective-C method + // when it is referenced from within the @implementation itself. In this + // context, we interpret unavailable as a form of access control. + if (const auto *MD = dyn_cast(OffendingDecl)) { + if (const auto *Impl = dyn_cast(C)) { + if (MD->getClassInterface() == Impl->getClassInterface()) + return true; + } + } + } + + if (C->isUnavailable()) + return true; + return false; + }; + + do { + if (CheckContext(Ctx)) + return false; + + // An implementation implicitly has the availability of the interface. + // Unless it is "+load" method. + if (const auto *MethodD = dyn_cast(Ctx)) + if (MethodD->isClassMethod() && + MethodD->getSelector().getAsString() == "load") + return true; + + if (const auto *CatOrImpl = dyn_cast(Ctx)) { + if (const ObjCInterfaceDecl *Interface = CatOrImpl->getClassInterface()) + if (CheckContext(Interface)) + return false; + } + // A category implicitly has the availability of the interface. + else if (const auto *CatD = dyn_cast(Ctx)) + if (const ObjCInterfaceDecl *Interface = CatD->getClassInterface()) + if (CheckContext(Interface)) + return false; + } while ((Ctx = cast_or_null(Ctx->getDeclContext()))); + + return true; +} + +static bool +shouldDiagnoseAvailabilityByDefault(const ASTContext &Context, + const VersionTuple &DeploymentVersion, + const VersionTuple &DeclVersion) { + const auto &Triple = Context.getTargetInfo().getTriple(); + VersionTuple ForceAvailabilityFromVersion; + switch (Triple.getOS()) { + case llvm::Triple::IOS: + case llvm::Triple::TvOS: + ForceAvailabilityFromVersion = VersionTuple(/*Major=*/11); + break; + case llvm::Triple::WatchOS: + ForceAvailabilityFromVersion = VersionTuple(/*Major=*/4); + break; + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + ForceAvailabilityFromVersion = VersionTuple(/*Major=*/10, /*Minor=*/13); + break; + default: + // New targets should always warn about availability. + return Triple.getVendor() == llvm::Triple::Apple; + } + return DeploymentVersion >= ForceAvailabilityFromVersion || + DeclVersion >= ForceAvailabilityFromVersion; +} + +static NamedDecl *findEnclosingDeclToAnnotate(Decl *OrigCtx) { + for (Decl *Ctx = OrigCtx; Ctx; + Ctx = cast_or_null(Ctx->getDeclContext())) { + if (isa(Ctx) || isa(Ctx) || isa(Ctx)) + return cast(Ctx); + if (auto *CD = dyn_cast(Ctx)) { + if (auto *Imp = dyn_cast(Ctx)) + return Imp->getClassInterface(); + return CD; + } + } + + return dyn_cast(OrigCtx); +} + +namespace { + +struct AttributeInsertion { + StringRef Prefix; + SourceLocation Loc; + StringRef Suffix; + + static AttributeInsertion createInsertionAfter(const NamedDecl *D) { + return {" ", D->getEndLoc(), ""}; + } + static AttributeInsertion createInsertionAfter(SourceLocation Loc) { + return {" ", Loc, ""}; + } + static AttributeInsertion createInsertionBefore(const NamedDecl *D) { + return {"", D->getBeginLoc(), "\n"}; + } +}; + +} // end anonymous namespace + +/// Tries to parse a string as ObjC method name. +/// +/// \param Name The string to parse. Expected to originate from availability +/// attribute argument. +/// \param SlotNames The vector that will be populated with slot names. In case +/// of unsuccessful parsing can contain invalid data. +/// \returns A number of method parameters if parsing was successful, None +/// otherwise. +static Optional +tryParseObjCMethodName(StringRef Name, SmallVectorImpl &SlotNames, + const LangOptions &LangOpts) { + // Accept replacements starting with - or + as valid ObjC method names. + if (!Name.empty() && (Name.front() == '-' || Name.front() == '+')) + Name = Name.drop_front(1); + if (Name.empty()) + return None; + Name.split(SlotNames, ':'); + unsigned NumParams; + if (Name.back() == ':') { + // Remove an empty string at the end that doesn't represent any slot. + SlotNames.pop_back(); + NumParams = SlotNames.size(); + } else { + if (SlotNames.size() != 1) + // Not a valid method name, just a colon-separated string. + return None; + NumParams = 0; + } + // Verify all slot names are valid. + bool AllowDollar = LangOpts.DollarIdents; + for (StringRef S : SlotNames) { + if (S.empty()) + continue; + if (!isValidIdentifier(S, AllowDollar)) + return None; + } + return NumParams; +} + +/// Returns a source location in which it's appropriate to insert a new +/// attribute for the given declaration \D. +static Optional +createAttributeInsertion(const NamedDecl *D, const SourceManager &SM, + const LangOptions &LangOpts) { + if (isa(D)) + return AttributeInsertion::createInsertionAfter(D); + if (const auto *MD = dyn_cast(D)) { + if (MD->hasBody()) + return None; + return AttributeInsertion::createInsertionAfter(D); + } + if (const auto *TD = dyn_cast(D)) { + SourceLocation Loc = + Lexer::getLocForEndOfToken(TD->getInnerLocStart(), 0, SM, LangOpts); + if (Loc.isInvalid()) + return None; + // Insert after the 'struct'/whatever keyword. + return AttributeInsertion::createInsertionAfter(Loc); + } + return AttributeInsertion::createInsertionBefore(D); +} + +/// Actually emit an availability diagnostic for a reference to an unavailable +/// decl. +/// +/// \param Ctx The context that the reference occurred in +/// \param ReferringDecl The exact declaration that was referenced. +/// \param OffendingDecl A related decl to \c ReferringDecl that has an +/// availability attribute corresponding to \c K attached to it. Note that this +/// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and +/// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl +/// and OffendingDecl is the EnumDecl. +static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, + Decl *Ctx, const NamedDecl *ReferringDecl, + const NamedDecl *OffendingDecl, + StringRef Message, + ArrayRef Locs, + const ObjCInterfaceDecl *UnknownObjCClass, + const ObjCPropertyDecl *ObjCProperty, + bool ObjCPropertyAccess) { + // Diagnostics for deprecated or unavailable. + unsigned diag, diag_message, diag_fwdclass_message; + unsigned diag_available_here = diag::note_availability_specified_here; + SourceLocation NoteLocation = OffendingDecl->getLocation(); + + // Matches 'diag::note_property_attribute' options. + unsigned property_note_select; + + // Matches diag::note_availability_specified_here. + unsigned available_here_select_kind; + + VersionTuple DeclVersion; + if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl)) + DeclVersion = AA->getIntroduced(); + + if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx, + OffendingDecl)) + return; + + SourceLocation Loc = Locs.front(); + + // The declaration can have multiple availability attributes, we are looking + // at one of them. + const AvailabilityAttr *A = getAttrForPlatform(S.Context, OffendingDecl); + if (A && A->isInherited()) { + for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl; + Redecl = Redecl->getPreviousDecl()) { + const AvailabilityAttr *AForRedecl = + getAttrForPlatform(S.Context, Redecl); + if (AForRedecl && !AForRedecl->isInherited()) { + // If D is a declaration with inherited attributes, the note should + // point to the declaration with actual attributes. + NoteLocation = Redecl->getLocation(); + break; + } + } + } + + switch (K) { + case AR_NotYetIntroduced: { + // We would like to emit the diagnostic even if -Wunguarded-availability is + // not specified for deployment targets >= to iOS 11 or equivalent or + // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or + // later. + const AvailabilityAttr *AA = + getAttrForPlatform(S.getASTContext(), OffendingDecl); + VersionTuple Introduced = AA->getIntroduced(); + + bool UseNewWarning = shouldDiagnoseAvailabilityByDefault( + S.Context, S.Context.getTargetInfo().getPlatformMinVersion(), + Introduced); + unsigned Warning = UseNewWarning ? diag::warn_unguarded_availability_new + : diag::warn_unguarded_availability; + + std::string PlatformName = AvailabilityAttr::getPrettyPlatformName( + S.getASTContext().getTargetInfo().getPlatformName()); + + S.Diag(Loc, Warning) << OffendingDecl << PlatformName + << Introduced.getAsString(); + + S.Diag(OffendingDecl->getLocation(), + diag::note_partial_availability_specified_here) + << OffendingDecl << PlatformName << Introduced.getAsString() + << S.Context.getTargetInfo().getPlatformMinVersion().getAsString(); + + if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) { + if (const auto *TD = dyn_cast(Enclosing)) + if (TD->getDeclName().isEmpty()) { + S.Diag(TD->getLocation(), + diag::note_decl_unguarded_availability_silence) + << /*Anonymous*/ 1 << TD->getKindName(); + return; + } + auto FixitNoteDiag = + S.Diag(Enclosing->getLocation(), + diag::note_decl_unguarded_availability_silence) + << /*Named*/ 0 << Enclosing; + // Don't offer a fixit for declarations with availability attributes. + if (Enclosing->hasAttr()) + return; + if (!S.getPreprocessor().isMacroDefined("API_AVAILABLE")) + return; + Optional Insertion = createAttributeInsertion( + Enclosing, S.getSourceManager(), S.getLangOpts()); + if (!Insertion) + return; + std::string PlatformName = + AvailabilityAttr::getPlatformNameSourceSpelling( + S.getASTContext().getTargetInfo().getPlatformName()) + .lower(); + std::string Introduced = + OffendingDecl->getVersionIntroduced().getAsString(); + FixitNoteDiag << FixItHint::CreateInsertion( + Insertion->Loc, + (llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" + PlatformName + + "(" + Introduced + "))" + Insertion->Suffix) + .str()); + } + return; + } + case AR_Deprecated: + diag = !ObjCPropertyAccess ? diag::warn_deprecated + : diag::warn_property_method_deprecated; + diag_message = diag::warn_deprecated_message; + diag_fwdclass_message = diag::warn_deprecated_fwdclass_message; + property_note_select = /* deprecated */ 0; + available_here_select_kind = /* deprecated */ 2; + if (const auto *AL = OffendingDecl->getAttr()) + NoteLocation = AL->getLocation(); + break; + + case AR_Unavailable: + diag = !ObjCPropertyAccess ? diag::err_unavailable + : diag::err_property_method_unavailable; + diag_message = diag::err_unavailable_message; + diag_fwdclass_message = diag::warn_unavailable_fwdclass_message; + property_note_select = /* unavailable */ 1; + available_here_select_kind = /* unavailable */ 0; + + if (auto AL = OffendingDecl->getAttr()) { + if (AL->isImplicit() && AL->getImplicitReason()) { + // Most of these failures are due to extra restrictions in ARC; + // reflect that in the primary diagnostic when applicable. + auto flagARCError = [&] { + if (S.getLangOpts().ObjCAutoRefCount && + S.getSourceManager().isInSystemHeader( + OffendingDecl->getLocation())) + diag = diag::err_unavailable_in_arc; + }; + + switch (AL->getImplicitReason()) { + case UnavailableAttr::IR_None: break; + + case UnavailableAttr::IR_ARCForbiddenType: + flagARCError(); + diag_available_here = diag::note_arc_forbidden_type; + break; + + case UnavailableAttr::IR_ForbiddenWeak: + if (S.getLangOpts().ObjCWeakRuntime) + diag_available_here = diag::note_arc_weak_disabled; + else + diag_available_here = diag::note_arc_weak_no_runtime; + break; + + case UnavailableAttr::IR_ARCForbiddenConversion: + flagARCError(); + diag_available_here = diag::note_performs_forbidden_arc_conversion; + break; + + case UnavailableAttr::IR_ARCInitReturnsUnrelated: + flagARCError(); + diag_available_here = diag::note_arc_init_returns_unrelated; + break; + + case UnavailableAttr::IR_ARCFieldWithOwnership: + flagARCError(); + diag_available_here = diag::note_arc_field_with_ownership; + break; + } + } + } + break; + + case AR_Available: + llvm_unreachable("Warning for availability of available declaration?"); + } + + SmallVector FixIts; + if (K == AR_Deprecated) { + StringRef Replacement; + if (auto AL = OffendingDecl->getAttr()) + Replacement = AL->getReplacement(); + if (auto AL = getAttrForPlatform(S.Context, OffendingDecl)) + Replacement = AL->getReplacement(); + + CharSourceRange UseRange; + if (!Replacement.empty()) + UseRange = + CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc)); + if (UseRange.isValid()) { + if (const auto *MethodDecl = dyn_cast(ReferringDecl)) { + Selector Sel = MethodDecl->getSelector(); + SmallVector SelectorSlotNames; + Optional NumParams = tryParseObjCMethodName( + Replacement, SelectorSlotNames, S.getLangOpts()); + if (NumParams && NumParams.getValue() == Sel.getNumArgs()) { + assert(SelectorSlotNames.size() == Locs.size()); + for (unsigned I = 0; I < Locs.size(); ++I) { + if (!Sel.getNameForSlot(I).empty()) { + CharSourceRange NameRange = CharSourceRange::getCharRange( + Locs[I], S.getLocForEndOfToken(Locs[I])); + FixIts.push_back(FixItHint::CreateReplacement( + NameRange, SelectorSlotNames[I])); + } else + FixIts.push_back( + FixItHint::CreateInsertion(Locs[I], SelectorSlotNames[I])); + } + } else + FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement)); + } else + FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement)); + } + } + + if (!Message.empty()) { + S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts; + if (ObjCProperty) + S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute) + << ObjCProperty->getDeclName() << property_note_select; + } else if (!UnknownObjCClass) { + S.Diag(Loc, diag) << ReferringDecl << FixIts; + if (ObjCProperty) + S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute) + << ObjCProperty->getDeclName() << property_note_select; + } else { + S.Diag(Loc, diag_fwdclass_message) << ReferringDecl << FixIts; + S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class); + } + + S.Diag(NoteLocation, diag_available_here) + << OffendingDecl << available_here_select_kind; +} + +void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) { + assert(DD.Kind == DelayedDiagnostic::Availability && + "Expected an availability diagnostic here"); + + DD.Triggered = true; + DoEmitAvailabilityWarning( + *this, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(), + DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(), + DD.getAvailabilitySelectorLocs(), DD.getUnknownObjCClass(), + DD.getObjCProperty(), false); +} + +static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR, + const NamedDecl *ReferringDecl, + const NamedDecl *OffendingDecl, + StringRef Message, + ArrayRef Locs, + const ObjCInterfaceDecl *UnknownObjCClass, + const ObjCPropertyDecl *ObjCProperty, + bool ObjCPropertyAccess) { + // Delay if we're currently parsing a declaration. + if (S.DelayedDiagnostics.shouldDelayDiagnostics()) { + S.DelayedDiagnostics.add( + DelayedDiagnostic::makeAvailability( + AR, Locs, ReferringDecl, OffendingDecl, UnknownObjCClass, + ObjCProperty, Message, ObjCPropertyAccess)); + return; + } + + Decl *Ctx = cast(S.getCurLexicalContext()); + DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl, + Message, Locs, UnknownObjCClass, ObjCProperty, + ObjCPropertyAccess); +} + +namespace { + +/// Returns true if the given statement can be a body-like child of \p Parent. +bool isBodyLikeChildStmt(const Stmt *S, const Stmt *Parent) { + switch (Parent->getStmtClass()) { + case Stmt::IfStmtClass: + return cast(Parent)->getThen() == S || + cast(Parent)->getElse() == S; + case Stmt::WhileStmtClass: + return cast(Parent)->getBody() == S; + case Stmt::DoStmtClass: + return cast(Parent)->getBody() == S; + case Stmt::ForStmtClass: + return cast(Parent)->getBody() == S; + case Stmt::CXXForRangeStmtClass: + return cast(Parent)->getBody() == S; + case Stmt::ObjCForCollectionStmtClass: + return cast(Parent)->getBody() == S; + case Stmt::CaseStmtClass: + case Stmt::DefaultStmtClass: + return cast(Parent)->getSubStmt() == S; + default: + return false; + } +} + +class StmtUSEFinder : public RecursiveASTVisitor { + const Stmt *Target; + +public: + bool VisitStmt(Stmt *S) { return S != Target; } + + /// Returns true if the given statement is present in the given declaration. + static bool isContained(const Stmt *Target, const Decl *D) { + StmtUSEFinder Visitor; + Visitor.Target = Target; + return !Visitor.TraverseDecl(const_cast(D)); + } +}; + +/// Traverses the AST and finds the last statement that used a given +/// declaration. +class LastDeclUSEFinder : public RecursiveASTVisitor { + const Decl *D; + +public: + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + if (DRE->getDecl() == D) + return false; + return true; + } + + static const Stmt *findLastStmtThatUsesDecl(const Decl *D, + const CompoundStmt *Scope) { + LastDeclUSEFinder Visitor; + Visitor.D = D; + for (auto I = Scope->body_rbegin(), E = Scope->body_rend(); I != E; ++I) { + const Stmt *S = *I; + if (!Visitor.TraverseStmt(const_cast(S))) + return S; + } + return nullptr; + } +}; + +/// This class implements -Wunguarded-availability. +/// +/// This is done with a traversal of the AST of a function that makes reference +/// to a partially available declaration. Whenever we encounter an \c if of the +/// form: \c if(@available(...)), we use the version from the condition to visit +/// the then statement. +class DiagnoseUnguardedAvailability + : public RecursiveASTVisitor { + typedef RecursiveASTVisitor Base; + + Sema &SemaRef; + Decl *Ctx; + + /// Stack of potentially nested 'if (@available(...))'s. + SmallVector AvailabilityStack; + SmallVector StmtStack; + + void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range, + ObjCInterfaceDecl *ClassReceiver = nullptr); + +public: + DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx) + : SemaRef(SemaRef), Ctx(Ctx) { + AvailabilityStack.push_back( + SemaRef.Context.getTargetInfo().getPlatformMinVersion()); + } + + bool TraverseDecl(Decl *D) { + // Avoid visiting nested functions to prevent duplicate warnings. + if (!D || isa(D)) + return true; + return Base::TraverseDecl(D); + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return true; + StmtStack.push_back(S); + bool Result = Base::TraverseStmt(S); + StmtStack.pop_back(); + return Result; + } + + void IssueDiagnostics(Stmt *S) { TraverseStmt(S); } + + bool TraverseIfStmt(IfStmt *If); + + bool TraverseLambdaExpr(LambdaExpr *E) { return true; } + + // for 'case X:' statements, don't bother looking at the 'X'; it can't lead + // to any useful diagnostics. + bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); } + + bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { + if (PRE->isClassReceiver()) + DiagnoseDeclAvailability(PRE->getClassReceiver(), PRE->getReceiverLocation()); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { + if (ObjCMethodDecl *D = Msg->getMethodDecl()) { + ObjCInterfaceDecl *ID = nullptr; + QualType ReceiverTy = Msg->getClassReceiver(); + if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType()) + ID = ReceiverTy->getAsObjCInterfaceType()->getInterface(); + + DiagnoseDeclAvailability( + D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID); + } + return true; + } + + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + DiagnoseDeclAvailability(DRE->getDecl(), + SourceRange(DRE->getBeginLoc(), DRE->getEndLoc())); + return true; + } + + bool VisitMemberExpr(MemberExpr *ME) { + DiagnoseDeclAvailability(ME->getMemberDecl(), + SourceRange(ME->getBeginLoc(), ME->getEndLoc())); + return true; + } + + bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { + SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use) + << (!SemaRef.getLangOpts().ObjC); + return true; + } + + bool VisitTypeLoc(TypeLoc Ty); +}; + +void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( + NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) { + AvailabilityResult Result; + const NamedDecl *OffendingDecl; + std::tie(Result, OffendingDecl) = + ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass); + if (Result != AR_Available) { + // All other diagnostic kinds have already been handled in + // DiagnoseAvailabilityOfDecl. + if (Result != AR_NotYetIntroduced) + return; + + const AvailabilityAttr *AA = + getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl); + VersionTuple Introduced = AA->getIntroduced(); + + if (AvailabilityStack.back() >= Introduced) + return; + + // If the context of this function is less available than D, we should not + // emit a diagnostic. + if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, Ctx, + OffendingDecl)) + return; + + // We would like to emit the diagnostic even if -Wunguarded-availability is + // not specified for deployment targets >= to iOS 11 or equivalent or + // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or + // later. + unsigned DiagKind = + shouldDiagnoseAvailabilityByDefault( + SemaRef.Context, + SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced) + ? diag::warn_unguarded_availability_new + : diag::warn_unguarded_availability; + + std::string PlatformName = AvailabilityAttr::getPrettyPlatformName( + SemaRef.getASTContext().getTargetInfo().getPlatformName()); + + SemaRef.Diag(Range.getBegin(), DiagKind) + << Range << D << PlatformName << Introduced.getAsString(); + + SemaRef.Diag(OffendingDecl->getLocation(), + diag::note_partial_availability_specified_here) + << OffendingDecl << PlatformName << Introduced.getAsString() + << SemaRef.Context.getTargetInfo() + .getPlatformMinVersion() + .getAsString(); + + auto FixitDiag = + SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence) + << Range << D + << (SemaRef.getLangOpts().ObjC ? /*@available*/ 0 + : /*__builtin_available*/ 1); + + // Find the statement which should be enclosed in the if @available check. + if (StmtStack.empty()) + return; + const Stmt *StmtOfUse = StmtStack.back(); + const CompoundStmt *Scope = nullptr; + for (const Stmt *S : llvm::reverse(StmtStack)) { + if (const auto *CS = dyn_cast(S)) { + Scope = CS; + break; + } + if (isBodyLikeChildStmt(StmtOfUse, S)) { + // The declaration won't be seen outside of the statement, so we don't + // have to wrap the uses of any declared variables in if (@available). + // Therefore we can avoid setting Scope here. + break; + } + StmtOfUse = S; + } + const Stmt *LastStmtOfUse = nullptr; + if (isa(StmtOfUse) && Scope) { + for (const Decl *D : cast(StmtOfUse)->decls()) { + if (StmtUSEFinder::isContained(StmtStack.back(), D)) { + LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope); + break; + } + } + } + + const SourceManager &SM = SemaRef.getSourceManager(); + SourceLocation IfInsertionLoc = + SM.getExpansionLoc(StmtOfUse->getBeginLoc()); + SourceLocation StmtEndLoc = + SM.getExpansionRange( + (LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc()) + .getEnd(); + if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc)) + return; + + StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM); + const char *ExtraIndentation = " "; + std::string FixItString; + llvm::raw_string_ostream FixItOS(FixItString); + FixItOS << "if (" << (SemaRef.getLangOpts().ObjC ? "@available" + : "__builtin_available") + << "(" + << AvailabilityAttr::getPlatformNameSourceSpelling( + SemaRef.getASTContext().getTargetInfo().getPlatformName()) + << " " << Introduced.getAsString() << ", *)) {\n" + << Indentation << ExtraIndentation; + FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str()); + SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken( + StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/false); + if (ElseInsertionLoc.isInvalid()) + ElseInsertionLoc = + Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts()); + FixItOS.str().clear(); + FixItOS << "\n" + << Indentation << "} else {\n" + << Indentation << ExtraIndentation + << "// Fallback on earlier versions\n" + << Indentation << "}"; + FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str()); + } +} + +bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) { + const Type *TyPtr = Ty.getTypePtr(); + SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()}; + + if (Range.isInvalid()) + return true; + + if (const auto *TT = dyn_cast(TyPtr)) { + TagDecl *TD = TT->getDecl(); + DiagnoseDeclAvailability(TD, Range); + + } else if (const auto *TD = dyn_cast(TyPtr)) { + TypedefNameDecl *D = TD->getDecl(); + DiagnoseDeclAvailability(D, Range); + + } else if (const auto *ObjCO = dyn_cast(TyPtr)) { + if (NamedDecl *D = ObjCO->getInterface()) + DiagnoseDeclAvailability(D, Range); + } + + return true; +} + +bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) { + VersionTuple CondVersion; + if (auto *E = dyn_cast(If->getCond())) { + CondVersion = E->getVersion(); + + // If we're using the '*' case here or if this check is redundant, then we + // use the enclosing version to check both branches. + if (CondVersion.empty() || CondVersion <= AvailabilityStack.back()) + return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse()); + } else { + // This isn't an availability checking 'if', we can just continue. + return Base::TraverseIfStmt(If); + } + + AvailabilityStack.push_back(CondVersion); + bool ShouldContinue = TraverseStmt(If->getThen()); + AvailabilityStack.pop_back(); + + return ShouldContinue && TraverseStmt(If->getElse()); +} + +} // end anonymous namespace + +void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) { + Stmt *Body = nullptr; + + if (auto *FD = D->getAsFunction()) { + // FIXME: We only examine the pattern decl for availability violations now, + // but we should also examine instantiated templates. + if (FD->isTemplateInstantiation()) + return; + + Body = FD->getBody(); + } else if (auto *MD = dyn_cast(D)) + Body = MD->getBody(); + else if (auto *BD = dyn_cast(D)) + Body = BD->getBody(); + + assert(Body && "Need a body here!"); + + DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(Body); +} + +void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D, + ArrayRef Locs, + const ObjCInterfaceDecl *UnknownObjCClass, + bool ObjCPropertyAccess, + bool AvoidPartialAvailabilityChecks, + ObjCInterfaceDecl *ClassReceiver) { + std::string Message; + AvailabilityResult Result; + const NamedDecl* OffendingDecl; + // See if this declaration is unavailable, deprecated, or partial. + std::tie(Result, OffendingDecl) = + ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver); + if (Result == AR_Available) + return; + + if (Result == AR_NotYetIntroduced) { + if (AvoidPartialAvailabilityChecks) + return; + + // We need to know the @available context in the current function to + // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that + // when we're done parsing the current function. + if (getCurFunctionOrMethodDecl()) { + getEnclosingFunction()->HasPotentialAvailabilityViolations = true; + return; + } else if (getCurBlock() || getCurLambda()) { + getCurFunction()->HasPotentialAvailabilityViolations = true; + return; + } + } + + const ObjCPropertyDecl *ObjCPDecl = nullptr; + if (const auto *MD = dyn_cast(D)) { + if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) { + AvailabilityResult PDeclResult = PD->getAvailability(nullptr); + if (PDeclResult == Result) + ObjCPDecl = PD; + } + } + + EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs, + UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess); +} diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7777,534 +7777,6 @@ DD.Triggered = true; } -static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context, - const Decl *D) { - // Check each AvailabilityAttr to find the one for this platform. - for (const auto *A : D->attrs()) { - if (const auto *Avail = dyn_cast(A)) { - // FIXME: this is copied from CheckAvailability. We should try to - // de-duplicate. - - // Check if this is an App Extension "platform", and if so chop off - // the suffix for matching with the actual platform. - StringRef ActualPlatform = Avail->getPlatform()->getName(); - StringRef RealizedPlatform = ActualPlatform; - if (Context.getLangOpts().AppExt) { - size_t suffix = RealizedPlatform.rfind("_app_extension"); - if (suffix != StringRef::npos) - RealizedPlatform = RealizedPlatform.slice(0, suffix); - } - - StringRef TargetPlatform = Context.getTargetInfo().getPlatformName(); - - // Match the platform name. - if (RealizedPlatform == TargetPlatform) - return Avail; - } - } - return nullptr; -} - -/// The diagnostic we should emit for \c D, and the declaration that -/// originated it, or \c AR_Available. -/// -/// \param D The declaration to check. -/// \param Message If non-null, this will be populated with the message from -/// the availability attribute that is selected. -/// \param ClassReceiver If we're checking the the method of a class message -/// send, the class. Otherwise nullptr. -static std::pair -ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D, - std::string *Message, - ObjCInterfaceDecl *ClassReceiver) { - AvailabilityResult Result = D->getAvailability(Message); - - // For typedefs, if the typedef declaration appears available look - // to the underlying type to see if it is more restrictive. - while (const auto *TD = dyn_cast(D)) { - if (Result == AR_Available) { - if (const auto *TT = TD->getUnderlyingType()->getAs()) { - D = TT->getDecl(); - Result = D->getAvailability(Message); - continue; - } - } - break; - } - - // Forward class declarations get their attributes from their definition. - if (const auto *IDecl = dyn_cast(D)) { - if (IDecl->getDefinition()) { - D = IDecl->getDefinition(); - Result = D->getAvailability(Message); - } - } - - if (const auto *ECD = dyn_cast(D)) - if (Result == AR_Available) { - const DeclContext *DC = ECD->getDeclContext(); - if (const auto *TheEnumDecl = dyn_cast(DC)) { - Result = TheEnumDecl->getAvailability(Message); - D = TheEnumDecl; - } - } - - // For +new, infer availability from -init. - if (const auto *MD = dyn_cast(D)) { - if (S.NSAPIObj && ClassReceiver) { - ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod( - S.NSAPIObj->getInitSelector()); - if (Init && Result == AR_Available && MD->isClassMethod() && - MD->getSelector() == S.NSAPIObj->getNewSelector() && - MD->definedInNSObject(S.getASTContext())) { - Result = Init->getAvailability(Message); - D = Init; - } - } - } - - return {Result, D}; -} - - -/// whether we should emit a diagnostic for \c K and \c DeclVersion in -/// the context of \c Ctx. For example, we should emit an unavailable diagnostic -/// in a deprecated context, but not the other way around. -static bool -ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K, - VersionTuple DeclVersion, Decl *Ctx, - const NamedDecl *OffendingDecl) { - assert(K != AR_Available && "Expected an unavailable declaration here!"); - - // Checks if we should emit the availability diagnostic in the context of C. - auto CheckContext = [&](const Decl *C) { - if (K == AR_NotYetIntroduced) { - if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C)) - if (AA->getIntroduced() >= DeclVersion) - return true; - } else if (K == AR_Deprecated) { - if (C->isDeprecated()) - return true; - } else if (K == AR_Unavailable) { - // It is perfectly fine to refer to an 'unavailable' Objective-C method - // when it is referenced from within the @implementation itself. In this - // context, we interpret unavailable as a form of access control. - if (const auto *MD = dyn_cast(OffendingDecl)) { - if (const auto *Impl = dyn_cast(C)) { - if (MD->getClassInterface() == Impl->getClassInterface()) - return true; - } - } - } - - if (C->isUnavailable()) - return true; - return false; - }; - - do { - if (CheckContext(Ctx)) - return false; - - // An implementation implicitly has the availability of the interface. - // Unless it is "+load" method. - if (const auto *MethodD = dyn_cast(Ctx)) - if (MethodD->isClassMethod() && - MethodD->getSelector().getAsString() == "load") - return true; - - if (const auto *CatOrImpl = dyn_cast(Ctx)) { - if (const ObjCInterfaceDecl *Interface = CatOrImpl->getClassInterface()) - if (CheckContext(Interface)) - return false; - } - // A category implicitly has the availability of the interface. - else if (const auto *CatD = dyn_cast(Ctx)) - if (const ObjCInterfaceDecl *Interface = CatD->getClassInterface()) - if (CheckContext(Interface)) - return false; - } while ((Ctx = cast_or_null(Ctx->getDeclContext()))); - - return true; -} - -static bool -shouldDiagnoseAvailabilityByDefault(const ASTContext &Context, - const VersionTuple &DeploymentVersion, - const VersionTuple &DeclVersion) { - const auto &Triple = Context.getTargetInfo().getTriple(); - VersionTuple ForceAvailabilityFromVersion; - switch (Triple.getOS()) { - case llvm::Triple::IOS: - case llvm::Triple::TvOS: - ForceAvailabilityFromVersion = VersionTuple(/*Major=*/11); - break; - case llvm::Triple::WatchOS: - ForceAvailabilityFromVersion = VersionTuple(/*Major=*/4); - break; - case llvm::Triple::Darwin: - case llvm::Triple::MacOSX: - ForceAvailabilityFromVersion = VersionTuple(/*Major=*/10, /*Minor=*/13); - break; - default: - // New targets should always warn about availability. - return Triple.getVendor() == llvm::Triple::Apple; - } - return DeploymentVersion >= ForceAvailabilityFromVersion || - DeclVersion >= ForceAvailabilityFromVersion; -} - -static NamedDecl *findEnclosingDeclToAnnotate(Decl *OrigCtx) { - for (Decl *Ctx = OrigCtx; Ctx; - Ctx = cast_or_null(Ctx->getDeclContext())) { - if (isa(Ctx) || isa(Ctx) || isa(Ctx)) - return cast(Ctx); - if (auto *CD = dyn_cast(Ctx)) { - if (auto *Imp = dyn_cast(Ctx)) - return Imp->getClassInterface(); - return CD; - } - } - - return dyn_cast(OrigCtx); -} - -namespace { - -struct AttributeInsertion { - StringRef Prefix; - SourceLocation Loc; - StringRef Suffix; - - static AttributeInsertion createInsertionAfter(const NamedDecl *D) { - return {" ", D->getEndLoc(), ""}; - } - static AttributeInsertion createInsertionAfter(SourceLocation Loc) { - return {" ", Loc, ""}; - } - static AttributeInsertion createInsertionBefore(const NamedDecl *D) { - return {"", D->getBeginLoc(), "\n"}; - } -}; - -} // end anonymous namespace - -/// Tries to parse a string as ObjC method name. -/// -/// \param Name The string to parse. Expected to originate from availability -/// attribute argument. -/// \param SlotNames The vector that will be populated with slot names. In case -/// of unsuccessful parsing can contain invalid data. -/// \returns A number of method parameters if parsing was successful, None -/// otherwise. -static Optional -tryParseObjCMethodName(StringRef Name, SmallVectorImpl &SlotNames, - const LangOptions &LangOpts) { - // Accept replacements starting with - or + as valid ObjC method names. - if (!Name.empty() && (Name.front() == '-' || Name.front() == '+')) - Name = Name.drop_front(1); - if (Name.empty()) - return None; - Name.split(SlotNames, ':'); - unsigned NumParams; - if (Name.back() == ':') { - // Remove an empty string at the end that doesn't represent any slot. - SlotNames.pop_back(); - NumParams = SlotNames.size(); - } else { - if (SlotNames.size() != 1) - // Not a valid method name, just a colon-separated string. - return None; - NumParams = 0; - } - // Verify all slot names are valid. - bool AllowDollar = LangOpts.DollarIdents; - for (StringRef S : SlotNames) { - if (S.empty()) - continue; - if (!isValidIdentifier(S, AllowDollar)) - return None; - } - return NumParams; -} - -/// Returns a source location in which it's appropriate to insert a new -/// attribute for the given declaration \D. -static Optional -createAttributeInsertion(const NamedDecl *D, const SourceManager &SM, - const LangOptions &LangOpts) { - if (isa(D)) - return AttributeInsertion::createInsertionAfter(D); - if (const auto *MD = dyn_cast(D)) { - if (MD->hasBody()) - return None; - return AttributeInsertion::createInsertionAfter(D); - } - if (const auto *TD = dyn_cast(D)) { - SourceLocation Loc = - Lexer::getLocForEndOfToken(TD->getInnerLocStart(), 0, SM, LangOpts); - if (Loc.isInvalid()) - return None; - // Insert after the 'struct'/whatever keyword. - return AttributeInsertion::createInsertionAfter(Loc); - } - return AttributeInsertion::createInsertionBefore(D); -} - -/// Actually emit an availability diagnostic for a reference to an unavailable -/// decl. -/// -/// \param Ctx The context that the reference occurred in -/// \param ReferringDecl The exact declaration that was referenced. -/// \param OffendingDecl A related decl to \c ReferringDecl that has an -/// availability attribute corresponding to \c K attached to it. Note that this -/// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and -/// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl -/// and OffendingDecl is the EnumDecl. -static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K, - Decl *Ctx, const NamedDecl *ReferringDecl, - const NamedDecl *OffendingDecl, - StringRef Message, - ArrayRef Locs, - const ObjCInterfaceDecl *UnknownObjCClass, - const ObjCPropertyDecl *ObjCProperty, - bool ObjCPropertyAccess) { - // Diagnostics for deprecated or unavailable. - unsigned diag, diag_message, diag_fwdclass_message; - unsigned diag_available_here = diag::note_availability_specified_here; - SourceLocation NoteLocation = OffendingDecl->getLocation(); - - // Matches 'diag::note_property_attribute' options. - unsigned property_note_select; - - // Matches diag::note_availability_specified_here. - unsigned available_here_select_kind; - - VersionTuple DeclVersion; - if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl)) - DeclVersion = AA->getIntroduced(); - - if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx, - OffendingDecl)) - return; - - SourceLocation Loc = Locs.front(); - - // The declaration can have multiple availability attributes, we are looking - // at one of them. - const AvailabilityAttr *A = getAttrForPlatform(S.Context, OffendingDecl); - if (A && A->isInherited()) { - for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl; - Redecl = Redecl->getPreviousDecl()) { - const AvailabilityAttr *AForRedecl = - getAttrForPlatform(S.Context, Redecl); - if (AForRedecl && !AForRedecl->isInherited()) { - // If D is a declaration with inherited attributes, the note should - // point to the declaration with actual attributes. - NoteLocation = Redecl->getLocation(); - break; - } - } - } - - switch (K) { - case AR_NotYetIntroduced: { - // We would like to emit the diagnostic even if -Wunguarded-availability is - // not specified for deployment targets >= to iOS 11 or equivalent or - // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or - // later. - const AvailabilityAttr *AA = - getAttrForPlatform(S.getASTContext(), OffendingDecl); - VersionTuple Introduced = AA->getIntroduced(); - - bool UseNewWarning = shouldDiagnoseAvailabilityByDefault( - S.Context, S.Context.getTargetInfo().getPlatformMinVersion(), - Introduced); - unsigned Warning = UseNewWarning ? diag::warn_unguarded_availability_new - : diag::warn_unguarded_availability; - - std::string PlatformName = AvailabilityAttr::getPrettyPlatformName( - S.getASTContext().getTargetInfo().getPlatformName()); - - S.Diag(Loc, Warning) << OffendingDecl << PlatformName - << Introduced.getAsString(); - - S.Diag(OffendingDecl->getLocation(), - diag::note_partial_availability_specified_here) - << OffendingDecl << PlatformName << Introduced.getAsString() - << S.Context.getTargetInfo().getPlatformMinVersion().getAsString(); - - if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) { - if (const auto *TD = dyn_cast(Enclosing)) - if (TD->getDeclName().isEmpty()) { - S.Diag(TD->getLocation(), - diag::note_decl_unguarded_availability_silence) - << /*Anonymous*/ 1 << TD->getKindName(); - return; - } - auto FixitNoteDiag = - S.Diag(Enclosing->getLocation(), - diag::note_decl_unguarded_availability_silence) - << /*Named*/ 0 << Enclosing; - // Don't offer a fixit for declarations with availability attributes. - if (Enclosing->hasAttr()) - return; - if (!S.getPreprocessor().isMacroDefined("API_AVAILABLE")) - return; - Optional Insertion = createAttributeInsertion( - Enclosing, S.getSourceManager(), S.getLangOpts()); - if (!Insertion) - return; - std::string PlatformName = - AvailabilityAttr::getPlatformNameSourceSpelling( - S.getASTContext().getTargetInfo().getPlatformName()) - .lower(); - std::string Introduced = - OffendingDecl->getVersionIntroduced().getAsString(); - FixitNoteDiag << FixItHint::CreateInsertion( - Insertion->Loc, - (llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" + PlatformName + - "(" + Introduced + "))" + Insertion->Suffix) - .str()); - } - return; - } - case AR_Deprecated: - diag = !ObjCPropertyAccess ? diag::warn_deprecated - : diag::warn_property_method_deprecated; - diag_message = diag::warn_deprecated_message; - diag_fwdclass_message = diag::warn_deprecated_fwdclass_message; - property_note_select = /* deprecated */ 0; - available_here_select_kind = /* deprecated */ 2; - if (const auto *AL = OffendingDecl->getAttr()) - NoteLocation = AL->getLocation(); - break; - - case AR_Unavailable: - diag = !ObjCPropertyAccess ? diag::err_unavailable - : diag::err_property_method_unavailable; - diag_message = diag::err_unavailable_message; - diag_fwdclass_message = diag::warn_unavailable_fwdclass_message; - property_note_select = /* unavailable */ 1; - available_here_select_kind = /* unavailable */ 0; - - if (auto AL = OffendingDecl->getAttr()) { - if (AL->isImplicit() && AL->getImplicitReason()) { - // Most of these failures are due to extra restrictions in ARC; - // reflect that in the primary diagnostic when applicable. - auto flagARCError = [&] { - if (S.getLangOpts().ObjCAutoRefCount && - S.getSourceManager().isInSystemHeader( - OffendingDecl->getLocation())) - diag = diag::err_unavailable_in_arc; - }; - - switch (AL->getImplicitReason()) { - case UnavailableAttr::IR_None: break; - - case UnavailableAttr::IR_ARCForbiddenType: - flagARCError(); - diag_available_here = diag::note_arc_forbidden_type; - break; - - case UnavailableAttr::IR_ForbiddenWeak: - if (S.getLangOpts().ObjCWeakRuntime) - diag_available_here = diag::note_arc_weak_disabled; - else - diag_available_here = diag::note_arc_weak_no_runtime; - break; - - case UnavailableAttr::IR_ARCForbiddenConversion: - flagARCError(); - diag_available_here = diag::note_performs_forbidden_arc_conversion; - break; - - case UnavailableAttr::IR_ARCInitReturnsUnrelated: - flagARCError(); - diag_available_here = diag::note_arc_init_returns_unrelated; - break; - - case UnavailableAttr::IR_ARCFieldWithOwnership: - flagARCError(); - diag_available_here = diag::note_arc_field_with_ownership; - break; - } - } - } - break; - - case AR_Available: - llvm_unreachable("Warning for availability of available declaration?"); - } - - SmallVector FixIts; - if (K == AR_Deprecated) { - StringRef Replacement; - if (auto AL = OffendingDecl->getAttr()) - Replacement = AL->getReplacement(); - if (auto AL = getAttrForPlatform(S.Context, OffendingDecl)) - Replacement = AL->getReplacement(); - - CharSourceRange UseRange; - if (!Replacement.empty()) - UseRange = - CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc)); - if (UseRange.isValid()) { - if (const auto *MethodDecl = dyn_cast(ReferringDecl)) { - Selector Sel = MethodDecl->getSelector(); - SmallVector SelectorSlotNames; - Optional NumParams = tryParseObjCMethodName( - Replacement, SelectorSlotNames, S.getLangOpts()); - if (NumParams && NumParams.getValue() == Sel.getNumArgs()) { - assert(SelectorSlotNames.size() == Locs.size()); - for (unsigned I = 0; I < Locs.size(); ++I) { - if (!Sel.getNameForSlot(I).empty()) { - CharSourceRange NameRange = CharSourceRange::getCharRange( - Locs[I], S.getLocForEndOfToken(Locs[I])); - FixIts.push_back(FixItHint::CreateReplacement( - NameRange, SelectorSlotNames[I])); - } else - FixIts.push_back( - FixItHint::CreateInsertion(Locs[I], SelectorSlotNames[I])); - } - } else - FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement)); - } else - FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement)); - } - } - - if (!Message.empty()) { - S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts; - if (ObjCProperty) - S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute) - << ObjCProperty->getDeclName() << property_note_select; - } else if (!UnknownObjCClass) { - S.Diag(Loc, diag) << ReferringDecl << FixIts; - if (ObjCProperty) - S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute) - << ObjCProperty->getDeclName() << property_note_select; - } else { - S.Diag(Loc, diag_fwdclass_message) << ReferringDecl << FixIts; - S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class); - } - - S.Diag(NoteLocation, diag_available_here) - << OffendingDecl << available_here_select_kind; -} - -static void handleDelayedAvailabilityCheck(Sema &S, DelayedDiagnostic &DD, - Decl *Ctx) { - assert(DD.Kind == DelayedDiagnostic::Availability && - "Expected an availability diagnostic here"); - - DD.Triggered = true; - DoEmitAvailabilityWarning( - S, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(), - DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(), - DD.getAvailabilitySelectorLocs(), DD.getUnknownObjCClass(), - DD.getObjCProperty(), false); -} void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) { assert(DelayedDiagnostics.getCurrentPool()); @@ -8338,7 +7810,7 @@ // Don't bother giving deprecation/unavailable diagnostics if // the decl is invalid. if (!decl->isInvalidDecl()) - handleDelayedAvailabilityCheck(*this, diag, decl); + handleDelayedAvailabilityCheck(diag, decl); break; case DelayedDiagnostic::Access: @@ -8368,415 +7840,3 @@ assert(curPool && "re-emitting in undelayed context not supported"); curPool->steal(pool); } - -static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR, - const NamedDecl *ReferringDecl, - const NamedDecl *OffendingDecl, - StringRef Message, - ArrayRef Locs, - const ObjCInterfaceDecl *UnknownObjCClass, - const ObjCPropertyDecl *ObjCProperty, - bool ObjCPropertyAccess) { - // Delay if we're currently parsing a declaration. - if (S.DelayedDiagnostics.shouldDelayDiagnostics()) { - S.DelayedDiagnostics.add( - DelayedDiagnostic::makeAvailability( - AR, Locs, ReferringDecl, OffendingDecl, UnknownObjCClass, - ObjCProperty, Message, ObjCPropertyAccess)); - return; - } - - Decl *Ctx = cast(S.getCurLexicalContext()); - DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl, - Message, Locs, UnknownObjCClass, ObjCProperty, - ObjCPropertyAccess); -} - -namespace { - -/// Returns true if the given statement can be a body-like child of \p Parent. -bool isBodyLikeChildStmt(const Stmt *S, const Stmt *Parent) { - switch (Parent->getStmtClass()) { - case Stmt::IfStmtClass: - return cast(Parent)->getThen() == S || - cast(Parent)->getElse() == S; - case Stmt::WhileStmtClass: - return cast(Parent)->getBody() == S; - case Stmt::DoStmtClass: - return cast(Parent)->getBody() == S; - case Stmt::ForStmtClass: - return cast(Parent)->getBody() == S; - case Stmt::CXXForRangeStmtClass: - return cast(Parent)->getBody() == S; - case Stmt::ObjCForCollectionStmtClass: - return cast(Parent)->getBody() == S; - case Stmt::CaseStmtClass: - case Stmt::DefaultStmtClass: - return cast(Parent)->getSubStmt() == S; - default: - return false; - } -} - -class StmtUSEFinder : public RecursiveASTVisitor { - const Stmt *Target; - -public: - bool VisitStmt(Stmt *S) { return S != Target; } - - /// Returns true if the given statement is present in the given declaration. - static bool isContained(const Stmt *Target, const Decl *D) { - StmtUSEFinder Visitor; - Visitor.Target = Target; - return !Visitor.TraverseDecl(const_cast(D)); - } -}; - -/// Traverses the AST and finds the last statement that used a given -/// declaration. -class LastDeclUSEFinder : public RecursiveASTVisitor { - const Decl *D; - -public: - bool VisitDeclRefExpr(DeclRefExpr *DRE) { - if (DRE->getDecl() == D) - return false; - return true; - } - - static const Stmt *findLastStmtThatUsesDecl(const Decl *D, - const CompoundStmt *Scope) { - LastDeclUSEFinder Visitor; - Visitor.D = D; - for (auto I = Scope->body_rbegin(), E = Scope->body_rend(); I != E; ++I) { - const Stmt *S = *I; - if (!Visitor.TraverseStmt(const_cast(S))) - return S; - } - return nullptr; - } -}; - -/// This class implements -Wunguarded-availability. -/// -/// This is done with a traversal of the AST of a function that makes reference -/// to a partially available declaration. Whenever we encounter an \c if of the -/// form: \c if(@available(...)), we use the version from the condition to visit -/// the then statement. -class DiagnoseUnguardedAvailability - : public RecursiveASTVisitor { - typedef RecursiveASTVisitor Base; - - Sema &SemaRef; - Decl *Ctx; - - /// Stack of potentially nested 'if (@available(...))'s. - SmallVector AvailabilityStack; - SmallVector StmtStack; - - void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range, - ObjCInterfaceDecl *ClassReceiver = nullptr); - -public: - DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx) - : SemaRef(SemaRef), Ctx(Ctx) { - AvailabilityStack.push_back( - SemaRef.Context.getTargetInfo().getPlatformMinVersion()); - } - - bool TraverseDecl(Decl *D) { - // Avoid visiting nested functions to prevent duplicate warnings. - if (!D || isa(D)) - return true; - return Base::TraverseDecl(D); - } - - bool TraverseStmt(Stmt *S) { - if (!S) - return true; - StmtStack.push_back(S); - bool Result = Base::TraverseStmt(S); - StmtStack.pop_back(); - return Result; - } - - void IssueDiagnostics(Stmt *S) { TraverseStmt(S); } - - bool TraverseIfStmt(IfStmt *If); - - bool TraverseLambdaExpr(LambdaExpr *E) { return true; } - - // for 'case X:' statements, don't bother looking at the 'X'; it can't lead - // to any useful diagnostics. - bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); } - - bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { - if (PRE->isClassReceiver()) - DiagnoseDeclAvailability(PRE->getClassReceiver(), PRE->getReceiverLocation()); - return true; - } - - bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { - if (ObjCMethodDecl *D = Msg->getMethodDecl()) { - ObjCInterfaceDecl *ID = nullptr; - QualType ReceiverTy = Msg->getClassReceiver(); - if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType()) - ID = ReceiverTy->getAsObjCInterfaceType()->getInterface(); - - DiagnoseDeclAvailability( - D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID); - } - return true; - } - - bool VisitDeclRefExpr(DeclRefExpr *DRE) { - DiagnoseDeclAvailability(DRE->getDecl(), - SourceRange(DRE->getBeginLoc(), DRE->getEndLoc())); - return true; - } - - bool VisitMemberExpr(MemberExpr *ME) { - DiagnoseDeclAvailability(ME->getMemberDecl(), - SourceRange(ME->getBeginLoc(), ME->getEndLoc())); - return true; - } - - bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { - SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use) - << (!SemaRef.getLangOpts().ObjC); - return true; - } - - bool VisitTypeLoc(TypeLoc Ty); -}; - -void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability( - NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) { - AvailabilityResult Result; - const NamedDecl *OffendingDecl; - std::tie(Result, OffendingDecl) = - ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass); - if (Result != AR_Available) { - // All other diagnostic kinds have already been handled in - // DiagnoseAvailabilityOfDecl. - if (Result != AR_NotYetIntroduced) - return; - - const AvailabilityAttr *AA = - getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl); - VersionTuple Introduced = AA->getIntroduced(); - - if (AvailabilityStack.back() >= Introduced) - return; - - // If the context of this function is less available than D, we should not - // emit a diagnostic. - if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, Ctx, - OffendingDecl)) - return; - - // We would like to emit the diagnostic even if -Wunguarded-availability is - // not specified for deployment targets >= to iOS 11 or equivalent or - // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or - // later. - unsigned DiagKind = - shouldDiagnoseAvailabilityByDefault( - SemaRef.Context, - SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced) - ? diag::warn_unguarded_availability_new - : diag::warn_unguarded_availability; - - std::string PlatformName = AvailabilityAttr::getPrettyPlatformName( - SemaRef.getASTContext().getTargetInfo().getPlatformName()); - - SemaRef.Diag(Range.getBegin(), DiagKind) - << Range << D << PlatformName << Introduced.getAsString(); - - SemaRef.Diag(OffendingDecl->getLocation(), - diag::note_partial_availability_specified_here) - << OffendingDecl << PlatformName << Introduced.getAsString() - << SemaRef.Context.getTargetInfo() - .getPlatformMinVersion() - .getAsString(); - - auto FixitDiag = - SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence) - << Range << D - << (SemaRef.getLangOpts().ObjC ? /*@available*/ 0 - : /*__builtin_available*/ 1); - - // Find the statement which should be enclosed in the if @available check. - if (StmtStack.empty()) - return; - const Stmt *StmtOfUse = StmtStack.back(); - const CompoundStmt *Scope = nullptr; - for (const Stmt *S : llvm::reverse(StmtStack)) { - if (const auto *CS = dyn_cast(S)) { - Scope = CS; - break; - } - if (isBodyLikeChildStmt(StmtOfUse, S)) { - // The declaration won't be seen outside of the statement, so we don't - // have to wrap the uses of any declared variables in if (@available). - // Therefore we can avoid setting Scope here. - break; - } - StmtOfUse = S; - } - const Stmt *LastStmtOfUse = nullptr; - if (isa(StmtOfUse) && Scope) { - for (const Decl *D : cast(StmtOfUse)->decls()) { - if (StmtUSEFinder::isContained(StmtStack.back(), D)) { - LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope); - break; - } - } - } - - const SourceManager &SM = SemaRef.getSourceManager(); - SourceLocation IfInsertionLoc = - SM.getExpansionLoc(StmtOfUse->getBeginLoc()); - SourceLocation StmtEndLoc = - SM.getExpansionRange( - (LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc()) - .getEnd(); - if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc)) - return; - - StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM); - const char *ExtraIndentation = " "; - std::string FixItString; - llvm::raw_string_ostream FixItOS(FixItString); - FixItOS << "if (" << (SemaRef.getLangOpts().ObjC ? "@available" - : "__builtin_available") - << "(" - << AvailabilityAttr::getPlatformNameSourceSpelling( - SemaRef.getASTContext().getTargetInfo().getPlatformName()) - << " " << Introduced.getAsString() << ", *)) {\n" - << Indentation << ExtraIndentation; - FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str()); - SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken( - StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(), - /*SkipTrailingWhitespaceAndNewLine=*/false); - if (ElseInsertionLoc.isInvalid()) - ElseInsertionLoc = - Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts()); - FixItOS.str().clear(); - FixItOS << "\n" - << Indentation << "} else {\n" - << Indentation << ExtraIndentation - << "// Fallback on earlier versions\n" - << Indentation << "}"; - FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str()); - } -} - -bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) { - const Type *TyPtr = Ty.getTypePtr(); - SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()}; - - if (Range.isInvalid()) - return true; - - if (const auto *TT = dyn_cast(TyPtr)) { - TagDecl *TD = TT->getDecl(); - DiagnoseDeclAvailability(TD, Range); - - } else if (const auto *TD = dyn_cast(TyPtr)) { - TypedefNameDecl *D = TD->getDecl(); - DiagnoseDeclAvailability(D, Range); - - } else if (const auto *ObjCO = dyn_cast(TyPtr)) { - if (NamedDecl *D = ObjCO->getInterface()) - DiagnoseDeclAvailability(D, Range); - } - - return true; -} - -bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) { - VersionTuple CondVersion; - if (auto *E = dyn_cast(If->getCond())) { - CondVersion = E->getVersion(); - - // If we're using the '*' case here or if this check is redundant, then we - // use the enclosing version to check both branches. - if (CondVersion.empty() || CondVersion <= AvailabilityStack.back()) - return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse()); - } else { - // This isn't an availability checking 'if', we can just continue. - return Base::TraverseIfStmt(If); - } - - AvailabilityStack.push_back(CondVersion); - bool ShouldContinue = TraverseStmt(If->getThen()); - AvailabilityStack.pop_back(); - - return ShouldContinue && TraverseStmt(If->getElse()); -} - -} // end anonymous namespace - -void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) { - Stmt *Body = nullptr; - - if (auto *FD = D->getAsFunction()) { - // FIXME: We only examine the pattern decl for availability violations now, - // but we should also examine instantiated templates. - if (FD->isTemplateInstantiation()) - return; - - Body = FD->getBody(); - } else if (auto *MD = dyn_cast(D)) - Body = MD->getBody(); - else if (auto *BD = dyn_cast(D)) - Body = BD->getBody(); - - assert(Body && "Need a body here!"); - - DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(Body); -} - -void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D, - ArrayRef Locs, - const ObjCInterfaceDecl *UnknownObjCClass, - bool ObjCPropertyAccess, - bool AvoidPartialAvailabilityChecks, - ObjCInterfaceDecl *ClassReceiver) { - std::string Message; - AvailabilityResult Result; - const NamedDecl* OffendingDecl; - // See if this declaration is unavailable, deprecated, or partial. - std::tie(Result, OffendingDecl) = - ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver); - if (Result == AR_Available) - return; - - if (Result == AR_NotYetIntroduced) { - if (AvoidPartialAvailabilityChecks) - return; - - // We need to know the @available context in the current function to - // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that - // when we're done parsing the current function. - if (getCurFunctionOrMethodDecl()) { - getEnclosingFunction()->HasPotentialAvailabilityViolations = true; - return; - } else if (getCurBlock() || getCurLambda()) { - getCurFunction()->HasPotentialAvailabilityViolations = true; - return; - } - } - - const ObjCPropertyDecl *ObjCPDecl = nullptr; - if (const auto *MD = dyn_cast(D)) { - if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) { - AvailabilityResult PDeclResult = PD->getAvailability(nullptr); - if (PDeclResult == Result) - ObjCPDecl = PD; - } - } - - EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs, - UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess); -}