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 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 *> 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 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 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 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 229 static AttributeInsertion createInsertionAfter(const NamedDecl *D) { 230 return {" ", D->getEndLoc(), ""}; 231 } 232 static AttributeInsertion createInsertionAfter(SourceLocation Loc) { 233 return {" ", Loc, ""}; 234 } 235 static AttributeInsertion createInsertionBefore(const NamedDecl *D) { 236 return {"", D->getBeginLoc(), "\n"}; 237 } 238 }; 239 240 } // end anonymous namespace 241 242 /// Tries to parse a string as ObjC method name. 243 /// 244 /// \param Name The string to parse. Expected to originate from availability 245 /// attribute argument. 246 /// \param SlotNames The vector that will be populated with slot names. In case 247 /// of unsuccessful parsing can contain invalid data. 248 /// \returns A number of method parameters if parsing was successful, 249 /// std::nullopt otherwise. 250 static std::optional<unsigned> 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> 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. 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 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 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. 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: 611 bool VisitStmt(Stmt *S) { return S != Target; } 612 613 /// Returns true if the given statement is present in the given declaration. 614 static bool isContained(const Stmt *Target, const Decl *D) { 615 StmtUSEFinder Visitor; 616 Visitor.Target = Target; 617 return !Visitor.TraverseDecl(const_cast<Decl *>(D)); 618 } 619 }; 620 621 /// Traverses the AST and finds the last statement that used a given 622 /// declaration. 623 class LastDeclUSEFinder : public RecursiveASTVisitor<LastDeclUSEFinder> { 624 const Decl *D; 625 626 public: 627 bool VisitDeclRefExpr(DeclRefExpr *DRE) { 628 if (DRE->getDecl() == D) 629 return false; 630 return true; 631 } 632 633 static const Stmt *findLastStmtThatUsesDecl(const Decl *D, 634 const CompoundStmt *Scope) { 635 LastDeclUSEFinder Visitor; 636 Visitor.D = D; 637 for (const Stmt *S : llvm::reverse(Scope->body())) { 638 if (!Visitor.TraverseStmt(const_cast<Stmt *>(S))) 639 return S; 640 } 641 return nullptr; 642 } 643 }; 644 645 /// This class implements -Wunguarded-availability. 646 /// 647 /// This is done with a traversal of the AST of a function that makes reference 648 /// to a partially available declaration. Whenever we encounter an \c if of the 649 /// form: \c if(@available(...)), we use the version from the condition to visit 650 /// the then statement. 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: 666 DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx) 667 : SemaRef(SemaRef), Ctx(Ctx) { 668 AvailabilityStack.push_back( 669 SemaRef.Context.getTargetInfo().getPlatformMinVersion()); 670 } 671 672 bool TraverseStmt(Stmt *S) { 673 if (!S) 674 return true; 675 StmtStack.push_back(S); 676 bool Result = Base::TraverseStmt(S); 677 StmtStack.pop_back(); 678 return Result; 679 } 680 681 void IssueDiagnostics(Stmt *S) { TraverseStmt(S); } 682 683 bool TraverseIfStmt(IfStmt *If); 684 685 // for 'case X:' statements, don't bother looking at the 'X'; it can't lead 686 // to any useful diagnostics. 687 bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); } 688 689 bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { return true; } 690 691 bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { 692 if (ObjCMethodDecl *D = Msg->getMethodDecl()) { 693 ObjCInterfaceDecl *ID = nullptr; 694 QualType ReceiverTy = Msg->getClassReceiver(); 695 if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType()) 696 ID = ReceiverTy->getAsObjCInterfaceType()->getInterface(); 697 698 DiagnoseDeclAvailability( 699 D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID); 700 } 701 return true; 702 } 703 704 bool VisitDeclRefExpr(DeclRefExpr *DRE) { 705 DiagnoseDeclAvailability(DRE->getDecl(), 706 SourceRange(DRE->getBeginLoc(), DRE->getEndLoc())); 707 return true; 708 } 709 710 bool VisitMemberExpr(MemberExpr *ME) { 711 DiagnoseDeclAvailability(ME->getMemberDecl(), 712 SourceRange(ME->getBeginLoc(), ME->getEndLoc())); 713 return true; 714 } 715 716 bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { 717 SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use) 718 << (!SemaRef.getLangOpts().ObjC); 719 return true; 720 } 721 722 bool VisitTypeLoc(TypeLoc Ty); 723 }; 724 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 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 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 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 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 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