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 // 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
178 static bool
shouldDiagnoseAvailabilityByDefault(const ASTContext & Context,const VersionTuple & DeploymentVersion,const VersionTuple & DeclVersion)179 shouldDiagnoseAvailabilityByDefault(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
findEnclosingDeclToAnnotate(Decl * OrigCtx)207 static 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
222 namespace {
223
224 struct AttributeInsertion {
225 StringRef Prefix;
226 SourceLocation Loc;
227 StringRef Suffix;
228
createInsertionAfter__anon86a5619d0211::AttributeInsertion229 static AttributeInsertion createInsertionAfter(const NamedDecl *D) {
230 return {" ", D->getEndLoc(), ""};
231 }
createInsertionAfter__anon86a5619d0211::AttributeInsertion232 static AttributeInsertion createInsertionAfter(SourceLocation Loc) {
233 return {" ", Loc, ""};
234 }
createInsertionBefore__anon86a5619d0211::AttributeInsertion235 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.
250 static std::optional<unsigned>
tryParseObjCMethodName(StringRef Name,SmallVectorImpl<StringRef> & SlotNames,const LangOptions & LangOpts)251 tryParseObjCMethodName(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.
283 static std::optional<AttributeInsertion>
createAttributeInsertion(const NamedDecl * D,const SourceManager & SM,const LangOptions & LangOpts)284 createAttributeInsertion(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.
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)314 static 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
handleDelayedAvailabilityCheck(DelayedDiagnostic & DD,Decl * Ctx)546 void 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
EmitAvailabilityWarning(Sema & S,AvailabilityResult AR,const NamedDecl * ReferringDecl,const NamedDecl * OffendingDecl,StringRef Message,ArrayRef<SourceLocation> Locs,const ObjCInterfaceDecl * UnknownObjCClass,const ObjCPropertyDecl * ObjCProperty,bool ObjCPropertyAccess)558 static 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
581 namespace {
582
583 /// Returns true if the given statement can be a body-like child of \p Parent.
isBodyLikeChildStmt(const Stmt * S,const Stmt * Parent)584 bool 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
607 class StmtUSEFinder : public RecursiveASTVisitor<StmtUSEFinder> {
608 const Stmt *Target;
609
610 public:
VisitStmt(Stmt * S)611 bool VisitStmt(Stmt *S) { return S != Target; }
612
613 /// Returns true if the given statement is present in the given declaration.
isContained(const Stmt * Target,const Decl * D)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.
623 class LastDeclUSEFinder : public RecursiveASTVisitor<LastDeclUSEFinder> {
624 const Decl *D;
625
626 public:
VisitDeclRefExpr(DeclRefExpr * DRE)627 bool VisitDeclRefExpr(DeclRefExpr *DRE) {
628 if (DRE->getDecl() == D)
629 return false;
630 return true;
631 }
632
findLastStmtThatUsesDecl(const Decl * D,const CompoundStmt * Scope)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.
651 class 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
665 public:
DiagnoseUnguardedAvailability(Sema & SemaRef,Decl * Ctx)666 DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx)
667 : SemaRef(SemaRef), Ctx(Ctx) {
668 AvailabilityStack.push_back(
669 SemaRef.Context.getTargetInfo().getPlatformMinVersion());
670 }
671
TraverseStmt(Stmt * S)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
IssueDiagnostics(Stmt * S)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.
TraverseCaseStmt(CaseStmt * CS)687 bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); }
688
VisitObjCPropertyRefExpr(ObjCPropertyRefExpr * PRE)689 bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { return true; }
690
VisitObjCMessageExpr(ObjCMessageExpr * Msg)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
VisitDeclRefExpr(DeclRefExpr * DRE)704 bool VisitDeclRefExpr(DeclRefExpr *DRE) {
705 DiagnoseDeclAvailability(DRE->getDecl(),
706 SourceRange(DRE->getBeginLoc(), DRE->getEndLoc()));
707 return true;
708 }
709
VisitMemberExpr(MemberExpr * ME)710 bool VisitMemberExpr(MemberExpr *ME) {
711 DiagnoseDeclAvailability(ME->getMemberDecl(),
712 SourceRange(ME->getBeginLoc(), ME->getEndLoc()));
713 return true;
714 }
715
VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr * E)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
DiagnoseDeclAvailability(NamedDecl * D,SourceRange Range,ObjCInterfaceDecl * ReceiverClass)725 void 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
VisitTypeLoc(TypeLoc Ty)846 bool 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
TraverseIfStmt(IfStmt * If)869 bool 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
DiagnoseUnguardedAvailabilityViolations(Decl * D)892 void 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
getCurFunctionAvailabilityContext()917 FunctionScopeInfo *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
DiagnoseAvailabilityOfDecl(NamedDecl * D,ArrayRef<SourceLocation> Locs,const ObjCInterfaceDecl * UnknownObjCClass,bool ObjCPropertyAccess,bool AvoidPartialAvailabilityChecks,ObjCInterfaceDecl * ClassReceiver)928 void 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