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