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