1 //===--- SemaAvailability.cpp - Availability attribute handling -----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  This file processes the availability attribute.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/AST/Attr.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/AST/RecursiveASTVisitor.h"
16 #include "clang/Basic/DiagnosticSema.h"
17 #include "clang/Basic/TargetInfo.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Sema/DelayedDiagnostic.h"
20 #include "clang/Sema/ScopeInfo.h"
21 #include "clang/Sema/Sema.h"
22 #include <optional>
23 
24 using namespace clang;
25 using namespace sema;
26 
getAttrForPlatform(ASTContext & Context,const Decl * D)27 static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
28                                                   const Decl *D) {
29   // Check each AvailabilityAttr to find the one for this platform.
30   for (const auto *A : D->attrs()) {
31     if (const auto *Avail = dyn_cast<AvailabilityAttr>(A)) {
32       // FIXME: this is copied from CheckAvailability. We should try to
33       // de-duplicate.
34 
35       // Check if this is an App Extension "platform", and if so chop off
36       // the suffix for matching with the actual platform.
37       StringRef ActualPlatform = Avail->getPlatform()->getName();
38       StringRef RealizedPlatform = ActualPlatform;
39       if (Context.getLangOpts().AppExt) {
40         size_t suffix = RealizedPlatform.rfind("_app_extension");
41         if (suffix != StringRef::npos)
42           RealizedPlatform = RealizedPlatform.slice(0, suffix);
43       }
44 
45       StringRef TargetPlatform = Context.getTargetInfo().getPlatformName();
46 
47       // Match the platform name.
48       if (RealizedPlatform == TargetPlatform)
49         return Avail;
50     }
51   }
52   return nullptr;
53 }
54 
55 /// The diagnostic we should emit for \c D, and the declaration that
56 /// originated it, or \c AR_Available.
57 ///
58 /// \param D The declaration to check.
59 /// \param Message If non-null, this will be populated with the message from
60 /// the availability attribute that is selected.
61 /// \param ClassReceiver If we're checking the method of a class message
62 /// send, the class. Otherwise nullptr.
63 static std::pair<AvailabilityResult, const NamedDecl *>
ShouldDiagnoseAvailabilityOfDecl(Sema & S,const NamedDecl * D,std::string * Message,ObjCInterfaceDecl * ClassReceiver)64 ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,
65                                  std::string *Message,
66                                  ObjCInterfaceDecl *ClassReceiver) {
67   AvailabilityResult Result = D->getAvailability(Message);
68 
69   // For typedefs, if the typedef declaration appears available look
70   // to the underlying type to see if it is more restrictive.
71   while (const auto *TD = dyn_cast<TypedefNameDecl>(D)) {
72     if (Result == AR_Available) {
73       if (const auto *TT = TD->getUnderlyingType()->getAs<TagType>()) {
74         D = TT->getDecl();
75         Result = D->getAvailability(Message);
76         continue;
77       }
78     }
79     break;
80   }
81 
82   // Forward class declarations get their attributes from their definition.
83   if (const auto *IDecl = dyn_cast<ObjCInterfaceDecl>(D)) {
84     if (IDecl->getDefinition()) {
85       D = IDecl->getDefinition();
86       Result = D->getAvailability(Message);
87     }
88   }
89 
90   if (const auto *ECD = dyn_cast<EnumConstantDecl>(D))
91     if (Result == AR_Available) {
92       const DeclContext *DC = ECD->getDeclContext();
93       if (const auto *TheEnumDecl = dyn_cast<EnumDecl>(DC)) {
94         Result = TheEnumDecl->getAvailability(Message);
95         D = TheEnumDecl;
96       }
97     }
98 
99   // For +new, infer availability from -init.
100   if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
101     if (S.NSAPIObj && ClassReceiver) {
102       ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod(
103           S.NSAPIObj->getInitSelector());
104       if (Init && Result == AR_Available && MD->isClassMethod() &&
105           MD->getSelector() == S.NSAPIObj->getNewSelector() &&
106           MD->definedInNSObject(S.getASTContext())) {
107         Result = Init->getAvailability(Message);
108         D = Init;
109       }
110     }
111   }
112 
113   return {Result, D};
114 }
115 
116 
117 /// whether we should emit a diagnostic for \c K and \c DeclVersion in
118 /// the context of \c Ctx. For example, we should emit an unavailable diagnostic
119 /// in a deprecated context, but not the other way around.
120 static bool
ShouldDiagnoseAvailabilityInContext(Sema & S,AvailabilityResult K,VersionTuple DeclVersion,Decl * Ctx,const NamedDecl * OffendingDecl)121 ShouldDiagnoseAvailabilityInContext(Sema &S, AvailabilityResult K,
122                                     VersionTuple DeclVersion, Decl *Ctx,
123                                     const NamedDecl *OffendingDecl) {
124   assert(K != AR_Available && "Expected an unavailable declaration here!");
125 
126   // If this was defined using CF_OPTIONS, etc. then ignore the diagnostic.
127   auto DeclLoc = Ctx->getBeginLoc();
128   // This is only a problem in Foundation's C++ implementation for CF_OPTIONS.
129   if (DeclLoc.isMacroID() && S.getLangOpts().CPlusPlus &&
130       isa<TypedefDecl>(OffendingDecl)) {
131     StringRef MacroName = S.getPreprocessor().getImmediateMacroName(DeclLoc);
132     if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" ||
133         MacroName == "SWIFT_OPTIONS" || MacroName == "NS_OPTIONS") {
134       return false;
135     }
136   }
137 
138   // Checks if we should emit the availability diagnostic in the context of C.
139   auto CheckContext = [&](const Decl *C) {
140     if (K == AR_NotYetIntroduced) {
141       if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C))
142         if (AA->getIntroduced() >= DeclVersion)
143           return true;
144     } else if (K == AR_Deprecated) {
145       if (C->isDeprecated())
146         return true;
147     } else if (K == AR_Unavailable) {
148       // It is perfectly fine to refer to an 'unavailable' Objective-C method
149       // when it is referenced from within the @implementation itself. In this
150       // context, we interpret unavailable as a form of access control.
151       if (const auto *MD = dyn_cast<ObjCMethodDecl>(OffendingDecl)) {
152         if (const auto *Impl = dyn_cast<ObjCImplDecl>(C)) {
153           if (MD->getClassInterface() == Impl->getClassInterface())
154             return true;
155         }
156       }
157     }
158 
159     if (C->isUnavailable())
160       return true;
161     return false;
162   };
163 
164   do {
165     if (CheckContext(Ctx))
166       return false;
167 
168     // An implementation implicitly has the availability of the interface.
169     // Unless it is "+load" method.
170     if (const auto *MethodD = dyn_cast<ObjCMethodDecl>(Ctx))
171       if (MethodD->isClassMethod() &&
172           MethodD->getSelector().getAsString() == "load")
173         return true;
174 
175     if (const auto *CatOrImpl = dyn_cast<ObjCImplDecl>(Ctx)) {
176       if (const ObjCInterfaceDecl *Interface = CatOrImpl->getClassInterface())
177         if (CheckContext(Interface))
178           return false;
179     }
180     // A category implicitly has the availability of the interface.
181     else if (const auto *CatD = dyn_cast<ObjCCategoryDecl>(Ctx))
182       if (const ObjCInterfaceDecl *Interface = CatD->getClassInterface())
183         if (CheckContext(Interface))
184           return false;
185   } while ((Ctx = cast_or_null<Decl>(Ctx->getDeclContext())));
186 
187   return true;
188 }
189 
190 static bool
shouldDiagnoseAvailabilityByDefault(const ASTContext & Context,const VersionTuple & DeploymentVersion,const VersionTuple & DeclVersion)191 shouldDiagnoseAvailabilityByDefault(const ASTContext &Context,
192                                     const VersionTuple &DeploymentVersion,
193                                     const VersionTuple &DeclVersion) {
194   const auto &Triple = Context.getTargetInfo().getTriple();
195   VersionTuple ForceAvailabilityFromVersion;
196   switch (Triple.getOS()) {
197   case llvm::Triple::IOS:
198   case llvm::Triple::TvOS:
199     ForceAvailabilityFromVersion = VersionTuple(/*Major=*/11);
200     break;
201   case llvm::Triple::WatchOS:
202     ForceAvailabilityFromVersion = VersionTuple(/*Major=*/4);
203     break;
204   case llvm::Triple::Darwin:
205   case llvm::Triple::MacOSX:
206     ForceAvailabilityFromVersion = VersionTuple(/*Major=*/10, /*Minor=*/13);
207     break;
208   case llvm::Triple::ShaderModel:
209     // Always enable availability diagnostics for shader models.
210     return true;
211   default:
212     // New targets should always warn about availability.
213     return Triple.getVendor() == llvm::Triple::Apple;
214   }
215   return DeploymentVersion >= ForceAvailabilityFromVersion ||
216          DeclVersion >= ForceAvailabilityFromVersion;
217 }
218 
findEnclosingDeclToAnnotate(Decl * OrigCtx)219 static NamedDecl *findEnclosingDeclToAnnotate(Decl *OrigCtx) {
220   for (Decl *Ctx = OrigCtx; Ctx;
221        Ctx = cast_or_null<Decl>(Ctx->getDeclContext())) {
222     if (isa<TagDecl>(Ctx) || isa<FunctionDecl>(Ctx) || isa<ObjCMethodDecl>(Ctx))
223       return cast<NamedDecl>(Ctx);
224     if (auto *CD = dyn_cast<ObjCContainerDecl>(Ctx)) {
225       if (auto *Imp = dyn_cast<ObjCImplDecl>(Ctx))
226         return Imp->getClassInterface();
227       return CD;
228     }
229   }
230 
231   return dyn_cast<NamedDecl>(OrigCtx);
232 }
233 
234 namespace {
235 
236 struct AttributeInsertion {
237   StringRef Prefix;
238   SourceLocation Loc;
239   StringRef Suffix;
240 
createInsertionAfter__anon77acdd980211::AttributeInsertion241   static AttributeInsertion createInsertionAfter(const NamedDecl *D) {
242     return {" ", D->getEndLoc(), ""};
243   }
createInsertionAfter__anon77acdd980211::AttributeInsertion244   static AttributeInsertion createInsertionAfter(SourceLocation Loc) {
245     return {" ", Loc, ""};
246   }
createInsertionBefore__anon77acdd980211::AttributeInsertion247   static AttributeInsertion createInsertionBefore(const NamedDecl *D) {
248     return {"", D->getBeginLoc(), "\n"};
249   }
250 };
251 
252 } // end anonymous namespace
253 
254 /// Tries to parse a string as ObjC method name.
255 ///
256 /// \param Name The string to parse. Expected to originate from availability
257 /// attribute argument.
258 /// \param SlotNames The vector that will be populated with slot names. In case
259 /// of unsuccessful parsing can contain invalid data.
260 /// \returns A number of method parameters if parsing was successful,
261 /// std::nullopt otherwise.
262 static std::optional<unsigned>
tryParseObjCMethodName(StringRef Name,SmallVectorImpl<StringRef> & SlotNames,const LangOptions & LangOpts)263 tryParseObjCMethodName(StringRef Name, SmallVectorImpl<StringRef> &SlotNames,
264                        const LangOptions &LangOpts) {
265   // Accept replacements starting with - or + as valid ObjC method names.
266   if (!Name.empty() && (Name.front() == '-' || Name.front() == '+'))
267     Name = Name.drop_front(1);
268   if (Name.empty())
269     return std::nullopt;
270   Name.split(SlotNames, ':');
271   unsigned NumParams;
272   if (Name.back() == ':') {
273     // Remove an empty string at the end that doesn't represent any slot.
274     SlotNames.pop_back();
275     NumParams = SlotNames.size();
276   } else {
277     if (SlotNames.size() != 1)
278       // Not a valid method name, just a colon-separated string.
279       return std::nullopt;
280     NumParams = 0;
281   }
282   // Verify all slot names are valid.
283   bool AllowDollar = LangOpts.DollarIdents;
284   for (StringRef S : SlotNames) {
285     if (S.empty())
286       continue;
287     if (!isValidAsciiIdentifier(S, AllowDollar))
288       return std::nullopt;
289   }
290   return NumParams;
291 }
292 
293 /// Returns a source location in which it's appropriate to insert a new
294 /// attribute for the given declaration \D.
295 static std::optional<AttributeInsertion>
createAttributeInsertion(const NamedDecl * D,const SourceManager & SM,const LangOptions & LangOpts)296 createAttributeInsertion(const NamedDecl *D, const SourceManager &SM,
297                          const LangOptions &LangOpts) {
298   if (isa<ObjCPropertyDecl>(D))
299     return AttributeInsertion::createInsertionAfter(D);
300   if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
301     if (MD->hasBody())
302       return std::nullopt;
303     return AttributeInsertion::createInsertionAfter(D);
304   }
305   if (const auto *TD = dyn_cast<TagDecl>(D)) {
306     SourceLocation Loc =
307         Lexer::getLocForEndOfToken(TD->getInnerLocStart(), 0, SM, LangOpts);
308     if (Loc.isInvalid())
309       return std::nullopt;
310     // Insert after the 'struct'/whatever keyword.
311     return AttributeInsertion::createInsertionAfter(Loc);
312   }
313   return AttributeInsertion::createInsertionBefore(D);
314 }
315 
316 /// Actually emit an availability diagnostic for a reference to an unavailable
317 /// decl.
318 ///
319 /// \param Ctx The context that the reference occurred in
320 /// \param ReferringDecl The exact declaration that was referenced.
321 /// \param OffendingDecl A related decl to \c ReferringDecl that has an
322 /// availability attribute corresponding to \c K attached to it. Note that this
323 /// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and
324 /// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl
325 /// and OffendingDecl is the EnumDecl.
DoEmitAvailabilityWarning(Sema & S,AvailabilityResult K,Decl * Ctx,const NamedDecl * ReferringDecl,const NamedDecl * OffendingDecl,StringRef Message,ArrayRef<SourceLocation> Locs,const ObjCInterfaceDecl * UnknownObjCClass,const ObjCPropertyDecl * ObjCProperty,bool ObjCPropertyAccess)326 static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
327                                       Decl *Ctx, const NamedDecl *ReferringDecl,
328                                       const NamedDecl *OffendingDecl,
329                                       StringRef Message,
330                                       ArrayRef<SourceLocation> Locs,
331                                       const ObjCInterfaceDecl *UnknownObjCClass,
332                                       const ObjCPropertyDecl *ObjCProperty,
333                                       bool ObjCPropertyAccess) {
334   // Diagnostics for deprecated or unavailable.
335   unsigned diag, diag_message, diag_fwdclass_message;
336   unsigned diag_available_here = diag::note_availability_specified_here;
337   SourceLocation NoteLocation = OffendingDecl->getLocation();
338 
339   // Matches 'diag::note_property_attribute' options.
340   unsigned property_note_select;
341 
342   // Matches diag::note_availability_specified_here.
343   unsigned available_here_select_kind;
344 
345   VersionTuple DeclVersion;
346   if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl))
347     DeclVersion = AA->getIntroduced();
348 
349   if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx,
350                                            OffendingDecl))
351     return;
352 
353   SourceLocation Loc = Locs.front();
354 
355   // The declaration can have multiple availability attributes, we are looking
356   // at one of them.
357   const AvailabilityAttr *A = getAttrForPlatform(S.Context, OffendingDecl);
358   if (A && A->isInherited()) {
359     for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl;
360          Redecl = Redecl->getPreviousDecl()) {
361       const AvailabilityAttr *AForRedecl =
362           getAttrForPlatform(S.Context, Redecl);
363       if (AForRedecl && !AForRedecl->isInherited()) {
364         // If D is a declaration with inherited attributes, the note should
365         // point to the declaration with actual attributes.
366         NoteLocation = Redecl->getLocation();
367         break;
368       }
369     }
370   }
371 
372   switch (K) {
373   case AR_NotYetIntroduced: {
374     // We would like to emit the diagnostic even if -Wunguarded-availability is
375     // not specified for deployment targets >= to iOS 11 or equivalent or
376     // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
377     // later.
378     const AvailabilityAttr *AA =
379         getAttrForPlatform(S.getASTContext(), OffendingDecl);
380     VersionTuple Introduced = AA->getIntroduced();
381 
382     bool UseNewWarning = shouldDiagnoseAvailabilityByDefault(
383         S.Context, S.Context.getTargetInfo().getPlatformMinVersion(),
384         Introduced);
385     unsigned Warning = UseNewWarning ? diag::warn_unguarded_availability_new
386                                      : diag::warn_unguarded_availability;
387 
388     std::string PlatformName(AvailabilityAttr::getPrettyPlatformName(
389         S.getASTContext().getTargetInfo().getPlatformName()));
390 
391     S.Diag(Loc, Warning) << OffendingDecl << PlatformName
392                          << Introduced.getAsString();
393 
394     S.Diag(OffendingDecl->getLocation(),
395            diag::note_partial_availability_specified_here)
396         << OffendingDecl << PlatformName << Introduced.getAsString()
397         << S.Context.getTargetInfo().getPlatformMinVersion().getAsString();
398 
399     if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) {
400       if (const auto *TD = dyn_cast<TagDecl>(Enclosing))
401         if (TD->getDeclName().isEmpty()) {
402           S.Diag(TD->getLocation(),
403                  diag::note_decl_unguarded_availability_silence)
404               << /*Anonymous*/ 1 << TD->getKindName();
405           return;
406         }
407       auto FixitNoteDiag =
408           S.Diag(Enclosing->getLocation(),
409                  diag::note_decl_unguarded_availability_silence)
410           << /*Named*/ 0 << Enclosing;
411       // Don't offer a fixit for declarations with availability attributes.
412       if (Enclosing->hasAttr<AvailabilityAttr>())
413         return;
414       if (!S.getPreprocessor().isMacroDefined("API_AVAILABLE"))
415         return;
416       std::optional<AttributeInsertion> Insertion = createAttributeInsertion(
417           Enclosing, S.getSourceManager(), S.getLangOpts());
418       if (!Insertion)
419         return;
420       std::string PlatformName =
421           AvailabilityAttr::getPlatformNameSourceSpelling(
422               S.getASTContext().getTargetInfo().getPlatformName())
423               .lower();
424       std::string Introduced =
425           OffendingDecl->getVersionIntroduced().getAsString();
426       FixitNoteDiag << FixItHint::CreateInsertion(
427           Insertion->Loc,
428           (llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" + PlatformName +
429            "(" + Introduced + "))" + Insertion->Suffix)
430               .str());
431     }
432     return;
433   }
434   case AR_Deprecated:
435     diag = !ObjCPropertyAccess ? diag::warn_deprecated
436                                : diag::warn_property_method_deprecated;
437     diag_message = diag::warn_deprecated_message;
438     diag_fwdclass_message = diag::warn_deprecated_fwdclass_message;
439     property_note_select = /* deprecated */ 0;
440     available_here_select_kind = /* deprecated */ 2;
441     if (const auto *AL = OffendingDecl->getAttr<DeprecatedAttr>())
442       NoteLocation = AL->getLocation();
443     break;
444 
445   case AR_Unavailable:
446     diag = !ObjCPropertyAccess ? diag::err_unavailable
447                                : diag::err_property_method_unavailable;
448     diag_message = diag::err_unavailable_message;
449     diag_fwdclass_message = diag::warn_unavailable_fwdclass_message;
450     property_note_select = /* unavailable */ 1;
451     available_here_select_kind = /* unavailable */ 0;
452 
453     if (auto AL = OffendingDecl->getAttr<UnavailableAttr>()) {
454       if (AL->isImplicit() && AL->getImplicitReason()) {
455         // Most of these failures are due to extra restrictions in ARC;
456         // reflect that in the primary diagnostic when applicable.
457         auto flagARCError = [&] {
458           if (S.getLangOpts().ObjCAutoRefCount &&
459               S.getSourceManager().isInSystemHeader(
460                   OffendingDecl->getLocation()))
461             diag = diag::err_unavailable_in_arc;
462         };
463 
464         switch (AL->getImplicitReason()) {
465         case UnavailableAttr::IR_None: break;
466 
467         case UnavailableAttr::IR_ARCForbiddenType:
468           flagARCError();
469           diag_available_here = diag::note_arc_forbidden_type;
470           break;
471 
472         case UnavailableAttr::IR_ForbiddenWeak:
473           if (S.getLangOpts().ObjCWeakRuntime)
474             diag_available_here = diag::note_arc_weak_disabled;
475           else
476             diag_available_here = diag::note_arc_weak_no_runtime;
477           break;
478 
479         case UnavailableAttr::IR_ARCForbiddenConversion:
480           flagARCError();
481           diag_available_here = diag::note_performs_forbidden_arc_conversion;
482           break;
483 
484         case UnavailableAttr::IR_ARCInitReturnsUnrelated:
485           flagARCError();
486           diag_available_here = diag::note_arc_init_returns_unrelated;
487           break;
488 
489         case UnavailableAttr::IR_ARCFieldWithOwnership:
490           flagARCError();
491           diag_available_here = diag::note_arc_field_with_ownership;
492           break;
493         }
494       }
495     }
496     break;
497 
498   case AR_Available:
499     llvm_unreachable("Warning for availability of available declaration?");
500   }
501 
502   SmallVector<FixItHint, 12> FixIts;
503   if (K == AR_Deprecated) {
504     StringRef Replacement;
505     if (auto AL = OffendingDecl->getAttr<DeprecatedAttr>())
506       Replacement = AL->getReplacement();
507     if (auto AL = getAttrForPlatform(S.Context, OffendingDecl))
508       Replacement = AL->getReplacement();
509 
510     CharSourceRange UseRange;
511     if (!Replacement.empty())
512       UseRange =
513           CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc));
514     if (UseRange.isValid()) {
515       if (const auto *MethodDecl = dyn_cast<ObjCMethodDecl>(ReferringDecl)) {
516         Selector Sel = MethodDecl->getSelector();
517         SmallVector<StringRef, 12> SelectorSlotNames;
518         std::optional<unsigned> NumParams = tryParseObjCMethodName(
519             Replacement, SelectorSlotNames, S.getLangOpts());
520         if (NumParams && *NumParams == Sel.getNumArgs()) {
521           assert(SelectorSlotNames.size() == Locs.size());
522           for (unsigned I = 0; I < Locs.size(); ++I) {
523             if (!Sel.getNameForSlot(I).empty()) {
524               CharSourceRange NameRange = CharSourceRange::getCharRange(
525                   Locs[I], S.getLocForEndOfToken(Locs[I]));
526               FixIts.push_back(FixItHint::CreateReplacement(
527                   NameRange, SelectorSlotNames[I]));
528             } else
529               FixIts.push_back(
530                   FixItHint::CreateInsertion(Locs[I], SelectorSlotNames[I]));
531           }
532         } else
533           FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
534       } else
535         FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
536     }
537   }
538 
539   // We emit deprecation warning for deprecated specializations
540   // when their instantiation stacks originate outside
541   // of a system header, even if the diagnostics is suppresed at the
542   // point of definition.
543   SourceLocation InstantiationLoc =
544       S.getTopMostPointOfInstantiation(ReferringDecl);
545   bool ShouldAllowWarningInSystemHeader =
546       InstantiationLoc != Loc &&
547       !S.getSourceManager().isInSystemHeader(InstantiationLoc);
548   struct AllowWarningInSystemHeaders {
549     AllowWarningInSystemHeaders(DiagnosticsEngine &E,
550                                 bool AllowWarningInSystemHeaders)
551         : Engine(E), Prev(E.getSuppressSystemWarnings()) {
552       E.setSuppressSystemWarnings(!AllowWarningInSystemHeaders);
553     }
554     ~AllowWarningInSystemHeaders() { Engine.setSuppressSystemWarnings(Prev); }
555 
556   private:
557     DiagnosticsEngine &Engine;
558     bool Prev;
559   } SystemWarningOverrideRAII(S.getDiagnostics(),
560                               ShouldAllowWarningInSystemHeader);
561 
562   if (!Message.empty()) {
563     S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts;
564     if (ObjCProperty)
565       S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
566           << ObjCProperty->getDeclName() << property_note_select;
567   } else if (!UnknownObjCClass) {
568     S.Diag(Loc, diag) << ReferringDecl << FixIts;
569     if (ObjCProperty)
570       S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
571           << ObjCProperty->getDeclName() << property_note_select;
572   } else {
573     S.Diag(Loc, diag_fwdclass_message) << ReferringDecl << FixIts;
574     S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class);
575   }
576 
577   S.Diag(NoteLocation, diag_available_here)
578     << OffendingDecl << available_here_select_kind;
579 }
580 
handleDelayedAvailabilityCheck(DelayedDiagnostic & DD,Decl * Ctx)581 void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) {
582   assert(DD.Kind == DelayedDiagnostic::Availability &&
583          "Expected an availability diagnostic here");
584 
585   DD.Triggered = true;
586   DoEmitAvailabilityWarning(
587       *this, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(),
588       DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(),
589       DD.getAvailabilitySelectorLocs(), DD.getUnknownObjCClass(),
590       DD.getObjCProperty(), false);
591 }
592 
EmitAvailabilityWarning(Sema & S,AvailabilityResult AR,const NamedDecl * ReferringDecl,const NamedDecl * OffendingDecl,StringRef Message,ArrayRef<SourceLocation> Locs,const ObjCInterfaceDecl * UnknownObjCClass,const ObjCPropertyDecl * ObjCProperty,bool ObjCPropertyAccess)593 static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR,
594                                     const NamedDecl *ReferringDecl,
595                                     const NamedDecl *OffendingDecl,
596                                     StringRef Message,
597                                     ArrayRef<SourceLocation> Locs,
598                                     const ObjCInterfaceDecl *UnknownObjCClass,
599                                     const ObjCPropertyDecl *ObjCProperty,
600                                     bool ObjCPropertyAccess) {
601   // Delay if we're currently parsing a declaration.
602   if (S.DelayedDiagnostics.shouldDelayDiagnostics()) {
603     S.DelayedDiagnostics.add(
604         DelayedDiagnostic::makeAvailability(
605             AR, Locs, ReferringDecl, OffendingDecl, UnknownObjCClass,
606             ObjCProperty, Message, ObjCPropertyAccess));
607     return;
608   }
609 
610   Decl *Ctx = cast<Decl>(S.getCurLexicalContext());
611   DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl,
612                             Message, Locs, UnknownObjCClass, ObjCProperty,
613                             ObjCPropertyAccess);
614 }
615 
616 namespace {
617 
618 /// Returns true if the given statement can be a body-like child of \p Parent.
isBodyLikeChildStmt(const Stmt * S,const Stmt * Parent)619 bool isBodyLikeChildStmt(const Stmt *S, const Stmt *Parent) {
620   switch (Parent->getStmtClass()) {
621   case Stmt::IfStmtClass:
622     return cast<IfStmt>(Parent)->getThen() == S ||
623            cast<IfStmt>(Parent)->getElse() == S;
624   case Stmt::WhileStmtClass:
625     return cast<WhileStmt>(Parent)->getBody() == S;
626   case Stmt::DoStmtClass:
627     return cast<DoStmt>(Parent)->getBody() == S;
628   case Stmt::ForStmtClass:
629     return cast<ForStmt>(Parent)->getBody() == S;
630   case Stmt::CXXForRangeStmtClass:
631     return cast<CXXForRangeStmt>(Parent)->getBody() == S;
632   case Stmt::ObjCForCollectionStmtClass:
633     return cast<ObjCForCollectionStmt>(Parent)->getBody() == S;
634   case Stmt::CaseStmtClass:
635   case Stmt::DefaultStmtClass:
636     return cast<SwitchCase>(Parent)->getSubStmt() == S;
637   default:
638     return false;
639   }
640 }
641 
642 class StmtUSEFinder : public RecursiveASTVisitor<StmtUSEFinder> {
643   const Stmt *Target;
644 
645 public:
VisitStmt(Stmt * S)646   bool VisitStmt(Stmt *S) { return S != Target; }
647 
648   /// Returns true if the given statement is present in the given declaration.
isContained(const Stmt * Target,const Decl * D)649   static bool isContained(const Stmt *Target, const Decl *D) {
650     StmtUSEFinder Visitor;
651     Visitor.Target = Target;
652     return !Visitor.TraverseDecl(const_cast<Decl *>(D));
653   }
654 };
655 
656 /// Traverses the AST and finds the last statement that used a given
657 /// declaration.
658 class LastDeclUSEFinder : public RecursiveASTVisitor<LastDeclUSEFinder> {
659   const Decl *D;
660 
661 public:
VisitDeclRefExpr(DeclRefExpr * DRE)662   bool VisitDeclRefExpr(DeclRefExpr *DRE) {
663     if (DRE->getDecl() == D)
664       return false;
665     return true;
666   }
667 
findLastStmtThatUsesDecl(const Decl * D,const CompoundStmt * Scope)668   static const Stmt *findLastStmtThatUsesDecl(const Decl *D,
669                                               const CompoundStmt *Scope) {
670     LastDeclUSEFinder Visitor;
671     Visitor.D = D;
672     for (const Stmt *S : llvm::reverse(Scope->body())) {
673       if (!Visitor.TraverseStmt(const_cast<Stmt *>(S)))
674         return S;
675     }
676     return nullptr;
677   }
678 };
679 
680 /// This class implements -Wunguarded-availability.
681 ///
682 /// This is done with a traversal of the AST of a function that makes reference
683 /// to a partially available declaration. Whenever we encounter an \c if of the
684 /// form: \c if(@available(...)), we use the version from the condition to visit
685 /// the then statement.
686 class DiagnoseUnguardedAvailability
687     : public RecursiveASTVisitor<DiagnoseUnguardedAvailability> {
688   typedef RecursiveASTVisitor<DiagnoseUnguardedAvailability> Base;
689 
690   Sema &SemaRef;
691   Decl *Ctx;
692 
693   /// Stack of potentially nested 'if (@available(...))'s.
694   SmallVector<VersionTuple, 8> AvailabilityStack;
695   SmallVector<const Stmt *, 16> StmtStack;
696 
697   void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range,
698                                 ObjCInterfaceDecl *ClassReceiver = nullptr);
699 
700 public:
DiagnoseUnguardedAvailability(Sema & SemaRef,Decl * Ctx)701   DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx)
702       : SemaRef(SemaRef), Ctx(Ctx) {
703     AvailabilityStack.push_back(
704         SemaRef.Context.getTargetInfo().getPlatformMinVersion());
705   }
706 
TraverseStmt(Stmt * S)707   bool TraverseStmt(Stmt *S) {
708     if (!S)
709       return true;
710     StmtStack.push_back(S);
711     bool Result = Base::TraverseStmt(S);
712     StmtStack.pop_back();
713     return Result;
714   }
715 
IssueDiagnostics(Stmt * S)716   void IssueDiagnostics(Stmt *S) { TraverseStmt(S); }
717 
718   bool TraverseIfStmt(IfStmt *If);
719 
720   // for 'case X:' statements, don't bother looking at the 'X'; it can't lead
721   // to any useful diagnostics.
TraverseCaseStmt(CaseStmt * CS)722   bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); }
723 
VisitObjCPropertyRefExpr(ObjCPropertyRefExpr * PRE)724   bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { return true; }
725 
VisitObjCMessageExpr(ObjCMessageExpr * Msg)726   bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
727     if (ObjCMethodDecl *D = Msg->getMethodDecl()) {
728       ObjCInterfaceDecl *ID = nullptr;
729       QualType ReceiverTy = Msg->getClassReceiver();
730       if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType())
731         ID = ReceiverTy->getAsObjCInterfaceType()->getInterface();
732 
733       DiagnoseDeclAvailability(
734           D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID);
735     }
736     return true;
737   }
738 
VisitDeclRefExpr(DeclRefExpr * DRE)739   bool VisitDeclRefExpr(DeclRefExpr *DRE) {
740     DiagnoseDeclAvailability(DRE->getDecl(),
741                              SourceRange(DRE->getBeginLoc(), DRE->getEndLoc()));
742     return true;
743   }
744 
VisitMemberExpr(MemberExpr * ME)745   bool VisitMemberExpr(MemberExpr *ME) {
746     DiagnoseDeclAvailability(ME->getMemberDecl(),
747                              SourceRange(ME->getBeginLoc(), ME->getEndLoc()));
748     return true;
749   }
750 
VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr * E)751   bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) {
752     SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use)
753         << (!SemaRef.getLangOpts().ObjC);
754     return true;
755   }
756 
757   bool VisitTypeLoc(TypeLoc Ty);
758 };
759 
DiagnoseDeclAvailability(NamedDecl * D,SourceRange Range,ObjCInterfaceDecl * ReceiverClass)760 void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
761     NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) {
762   AvailabilityResult Result;
763   const NamedDecl *OffendingDecl;
764   std::tie(Result, OffendingDecl) =
765       ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass);
766   if (Result != AR_Available) {
767     // All other diagnostic kinds have already been handled in
768     // DiagnoseAvailabilityOfDecl.
769     if (Result != AR_NotYetIntroduced)
770       return;
771 
772     const AvailabilityAttr *AA =
773       getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl);
774     VersionTuple Introduced = AA->getIntroduced();
775 
776     if (AvailabilityStack.back() >= Introduced)
777       return;
778 
779     // If the context of this function is less available than D, we should not
780     // emit a diagnostic.
781     if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced, Ctx,
782                                              OffendingDecl))
783       return;
784 
785     // We would like to emit the diagnostic even if -Wunguarded-availability is
786     // not specified for deployment targets >= to iOS 11 or equivalent or
787     // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
788     // later.
789     unsigned DiagKind =
790         shouldDiagnoseAvailabilityByDefault(
791             SemaRef.Context,
792             SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced)
793             ? diag::warn_unguarded_availability_new
794             : diag::warn_unguarded_availability;
795 
796     std::string PlatformName(AvailabilityAttr::getPrettyPlatformName(
797         SemaRef.getASTContext().getTargetInfo().getPlatformName()));
798 
799     SemaRef.Diag(Range.getBegin(), DiagKind)
800         << Range << D << PlatformName << Introduced.getAsString();
801 
802     SemaRef.Diag(OffendingDecl->getLocation(),
803                  diag::note_partial_availability_specified_here)
804         << OffendingDecl << PlatformName << Introduced.getAsString()
805         << SemaRef.Context.getTargetInfo()
806                .getPlatformMinVersion()
807                .getAsString();
808 
809     auto FixitDiag =
810         SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
811         << Range << D
812         << (SemaRef.getLangOpts().ObjC ? /*@available*/ 0
813                                        : /*__builtin_available*/ 1);
814 
815     // Find the statement which should be enclosed in the if @available check.
816     if (StmtStack.empty())
817       return;
818     const Stmt *StmtOfUse = StmtStack.back();
819     const CompoundStmt *Scope = nullptr;
820     for (const Stmt *S : llvm::reverse(StmtStack)) {
821       if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
822         Scope = CS;
823         break;
824       }
825       if (isBodyLikeChildStmt(StmtOfUse, S)) {
826         // The declaration won't be seen outside of the statement, so we don't
827         // have to wrap the uses of any declared variables in if (@available).
828         // Therefore we can avoid setting Scope here.
829         break;
830       }
831       StmtOfUse = S;
832     }
833     const Stmt *LastStmtOfUse = nullptr;
834     if (isa<DeclStmt>(StmtOfUse) && Scope) {
835       for (const Decl *D : cast<DeclStmt>(StmtOfUse)->decls()) {
836         if (StmtUSEFinder::isContained(StmtStack.back(), D)) {
837           LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope);
838           break;
839         }
840       }
841     }
842 
843     const SourceManager &SM = SemaRef.getSourceManager();
844     SourceLocation IfInsertionLoc =
845         SM.getExpansionLoc(StmtOfUse->getBeginLoc());
846     SourceLocation StmtEndLoc =
847         SM.getExpansionRange(
848               (LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc())
849             .getEnd();
850     if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc))
851       return;
852 
853     StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM);
854     const char *ExtraIndentation = "    ";
855     std::string FixItString;
856     llvm::raw_string_ostream FixItOS(FixItString);
857     FixItOS << "if (" << (SemaRef.getLangOpts().ObjC ? "@available"
858                                                      : "__builtin_available")
859             << "("
860             << AvailabilityAttr::getPlatformNameSourceSpelling(
861                    SemaRef.getASTContext().getTargetInfo().getPlatformName())
862             << " " << Introduced.getAsString() << ", *)) {\n"
863             << Indentation << ExtraIndentation;
864     FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str());
865     SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken(
866         StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(),
867         /*SkipTrailingWhitespaceAndNewLine=*/false);
868     if (ElseInsertionLoc.isInvalid())
869       ElseInsertionLoc =
870           Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts());
871     FixItOS.str().clear();
872     FixItOS << "\n"
873             << Indentation << "} else {\n"
874             << Indentation << ExtraIndentation
875             << "// Fallback on earlier versions\n"
876             << Indentation << "}";
877     FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str());
878   }
879 }
880 
VisitTypeLoc(TypeLoc Ty)881 bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
882   const Type *TyPtr = Ty.getTypePtr();
883   SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()};
884 
885   if (Range.isInvalid())
886     return true;
887 
888   if (const auto *TT = dyn_cast<TagType>(TyPtr)) {
889     TagDecl *TD = TT->getDecl();
890     DiagnoseDeclAvailability(TD, Range);
891 
892   } else if (const auto *TD = dyn_cast<TypedefType>(TyPtr)) {
893     TypedefNameDecl *D = TD->getDecl();
894     DiagnoseDeclAvailability(D, Range);
895 
896   } else if (const auto *ObjCO = dyn_cast<ObjCObjectType>(TyPtr)) {
897     if (NamedDecl *D = ObjCO->getInterface())
898       DiagnoseDeclAvailability(D, Range);
899   }
900 
901   return true;
902 }
903 
TraverseIfStmt(IfStmt * If)904 bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
905   VersionTuple CondVersion;
906   if (auto *E = dyn_cast<ObjCAvailabilityCheckExpr>(If->getCond())) {
907     CondVersion = E->getVersion();
908 
909     // If we're using the '*' case here or if this check is redundant, then we
910     // use the enclosing version to check both branches.
911     if (CondVersion.empty() || CondVersion <= AvailabilityStack.back())
912       return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
913   } else {
914     // This isn't an availability checking 'if', we can just continue.
915     return Base::TraverseIfStmt(If);
916   }
917 
918   AvailabilityStack.push_back(CondVersion);
919   bool ShouldContinue = TraverseStmt(If->getThen());
920   AvailabilityStack.pop_back();
921 
922   return ShouldContinue && TraverseStmt(If->getElse());
923 }
924 
925 } // end anonymous namespace
926 
DiagnoseUnguardedAvailabilityViolations(Decl * D)927 void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) {
928   Stmt *Body = nullptr;
929 
930   if (auto *FD = D->getAsFunction()) {
931     // FIXME: We only examine the pattern decl for availability violations now,
932     // but we should also examine instantiated templates.
933     if (FD->isTemplateInstantiation())
934       return;
935 
936     Body = FD->getBody();
937 
938     if (auto *CD = dyn_cast<CXXConstructorDecl>(FD))
939       for (const CXXCtorInitializer *CI : CD->inits())
940         DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(CI->getInit());
941 
942   } else if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
943     Body = MD->getBody();
944   else if (auto *BD = dyn_cast<BlockDecl>(D))
945     Body = BD->getBody();
946 
947   assert(Body && "Need a body here!");
948 
949   DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(Body);
950 }
951 
getCurFunctionAvailabilityContext()952 FunctionScopeInfo *Sema::getCurFunctionAvailabilityContext() {
953   if (FunctionScopes.empty())
954     return nullptr;
955 
956   // Conservatively search the entire current function scope context for
957   // availability violations. This ensures we always correctly analyze nested
958   // classes, blocks, lambdas, etc. that may or may not be inside if(@available)
959   // checks themselves.
960   return FunctionScopes.front();
961 }
962 
DiagnoseAvailabilityOfDecl(NamedDecl * D,ArrayRef<SourceLocation> Locs,const ObjCInterfaceDecl * UnknownObjCClass,bool ObjCPropertyAccess,bool AvoidPartialAvailabilityChecks,ObjCInterfaceDecl * ClassReceiver)963 void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
964                                       ArrayRef<SourceLocation> Locs,
965                                       const ObjCInterfaceDecl *UnknownObjCClass,
966                                       bool ObjCPropertyAccess,
967                                       bool AvoidPartialAvailabilityChecks,
968                                       ObjCInterfaceDecl *ClassReceiver) {
969   std::string Message;
970   AvailabilityResult Result;
971   const NamedDecl* OffendingDecl;
972   // See if this declaration is unavailable, deprecated, or partial.
973   std::tie(Result, OffendingDecl) =
974       ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver);
975   if (Result == AR_Available)
976     return;
977 
978   if (Result == AR_NotYetIntroduced) {
979     if (AvoidPartialAvailabilityChecks)
980       return;
981 
982     // We need to know the @available context in the current function to
983     // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that
984     // when we're done parsing the current function.
985     if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext()) {
986       Context->HasPotentialAvailabilityViolations = true;
987       return;
988     }
989   }
990 
991   const ObjCPropertyDecl *ObjCPDecl = nullptr;
992   if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
993     if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
994       AvailabilityResult PDeclResult = PD->getAvailability(nullptr);
995       if (PDeclResult == Result)
996         ObjCPDecl = PD;
997     }
998   }
999 
1000   EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs,
1001                           UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess);
1002 }
1003