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

source code of clang/lib/Sema/SemaAvailability.cpp