llvm-project/clang/lib/Sema/SemaAvailability.cpp

1149 lines
43 KiB
C++

//===--- SemaAvailability.cpp - Availability attribute handling -----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file processes the availability attribute.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/StmtObjC.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/DelayedDiagnostic.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaObjC.h"
#include "llvm/ADT/StringRef.h"
#include <optional>
using namespace clang;
using namespace sema;
static bool hasMatchingEnvironmentOrNone(const ASTContext &Context,
const AvailabilityAttr *AA) {
IdentifierInfo *IIEnvironment = AA->getEnvironment();
auto Environment = Context.getTargetInfo().getTriple().getEnvironment();
if (!IIEnvironment || Environment == llvm::Triple::UnknownEnvironment)
return true;
llvm::Triple::EnvironmentType ET =
AvailabilityAttr::getEnvironmentType(IIEnvironment->getName());
return Environment == ET;
}
static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
const Decl *D) {
AvailabilityAttr const *PartialMatch = nullptr;
// Check each AvailabilityAttr to find the one for this platform.
// For multiple attributes with the same platform try to find one for this
// environment.
// The attribute is always on the FunctionDecl, not on the
// FunctionTemplateDecl.
if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(D))
D = FTD->getTemplatedDecl();
for (const auto *A : D->attrs()) {
if (const auto *Avail = dyn_cast<AvailabilityAttr>(A)) {
// FIXME: this is copied from CheckAvailability. We should try to
// de-duplicate.
// Check if this is an App Extension "platform", and if so chop off
// the suffix for matching with the actual platform.
StringRef ActualPlatform = Avail->getPlatform()->getName();
StringRef RealizedPlatform = ActualPlatform;
if (Context.getLangOpts().AppExt) {
size_t suffix = RealizedPlatform.rfind("_app_extension");
if (suffix != StringRef::npos)
RealizedPlatform = RealizedPlatform.slice(0, suffix);
}
StringRef TargetPlatform = Context.getTargetInfo().getPlatformName();
// Match the platform name.
if (RealizedPlatform == TargetPlatform) {
// Find the best matching attribute for this environment
if (hasMatchingEnvironmentOrNone(Context, Avail))
return Avail;
PartialMatch = Avail;
}
}
}
return PartialMatch;
}
/// The diagnostic we should emit for \c D, and the declaration that
/// originated it, or \c AR_Available.
///
/// \param D The declaration to check.
/// \param Message If non-null, this will be populated with the message from
/// the availability attribute that is selected.
/// \param ClassReceiver If we're checking the method of a class message
/// send, the class. Otherwise nullptr.
static std::pair<AvailabilityResult, const NamedDecl *>
ShouldDiagnoseAvailabilityOfDecl(Sema &S, const NamedDecl *D,
std::string *Message,
ObjCInterfaceDecl *ClassReceiver) {
AvailabilityResult Result = D->getAvailability(Message);
// For typedefs, if the typedef declaration appears available look
// to the underlying type to see if it is more restrictive.
while (const auto *TD = dyn_cast<TypedefNameDecl>(D)) {
if (Result != AR_Available)
break;
for (const Type *T = TD->getUnderlyingType().getTypePtr(); /**/; /**/) {
if (auto *TT = dyn_cast<TagType>(T)) {
D = TT->getDecl();
} else if (isa<SubstTemplateTypeParmType>(T)) {
// A Subst* node represents a use through a template.
// Any uses of the underlying declaration happened through it's template
// specialization.
goto done;
} else {
const Type *NextT =
T->getLocallyUnqualifiedSingleStepDesugaredType().getTypePtr();
if (NextT == T)
goto done;
T = NextT;
continue;
}
Result = D->getAvailability(Message);
break;
}
}
done:
// For alias templates, get the underlying declaration.
if (const auto *ADecl = dyn_cast<TypeAliasTemplateDecl>(D)) {
D = ADecl->getTemplatedDecl();
Result = D->getAvailability(Message);
}
// Forward class declarations get their attributes from their definition.
if (const auto *IDecl = dyn_cast<ObjCInterfaceDecl>(D)) {
if (IDecl->getDefinition()) {
D = IDecl->getDefinition();
Result = D->getAvailability(Message);
}
}
if (const auto *ECD = dyn_cast<EnumConstantDecl>(D))
if (Result == AR_Available) {
const DeclContext *DC = ECD->getDeclContext();
if (const auto *TheEnumDecl = dyn_cast<EnumDecl>(DC)) {
Result = TheEnumDecl->getAvailability(Message);
D = TheEnumDecl;
}
}
// For +new, infer availability from -init.
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (S.ObjC().NSAPIObj && ClassReceiver) {
ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod(
S.ObjC().NSAPIObj->getInitSelector());
if (Init && Result == AR_Available && MD->isClassMethod() &&
MD->getSelector() == S.ObjC().NSAPIObj->getNewSelector() &&
MD->definedInNSObject(S.getASTContext())) {
Result = Init->getAvailability(Message);
D = Init;
}
}
}
return {Result, D};
}
/// whether we should emit a diagnostic for \c K and \c DeclVersion in
/// the context of \c Ctx. For example, we should emit an unavailable diagnostic
/// in a deprecated context, but not the other way around.
static bool ShouldDiagnoseAvailabilityInContext(
Sema &S, AvailabilityResult K, VersionTuple DeclVersion,
const IdentifierInfo *DeclEnv, Decl *Ctx, const NamedDecl *OffendingDecl) {
assert(K != AR_Available && "Expected an unavailable declaration here!");
// If this was defined using CF_OPTIONS, etc. then ignore the diagnostic.
auto DeclLoc = Ctx->getBeginLoc();
// This is only a problem in Foundation's C++ implementation for CF_OPTIONS.
if (DeclLoc.isMacroID() && S.getLangOpts().CPlusPlus &&
isa<TypedefDecl>(OffendingDecl)) {
StringRef MacroName = S.getPreprocessor().getImmediateMacroName(DeclLoc);
if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" ||
MacroName == "SWIFT_OPTIONS" || MacroName == "NS_OPTIONS") {
return false;
}
}
// In HLSL, skip emitting diagnostic if the diagnostic mode is not set to
// strict (-fhlsl-strict-availability), or if the target is library and the
// availability is restricted to a specific environment/shader stage.
// For libraries the availability will be checked later in
// DiagnoseHLSLAvailability class once where the specific environment/shader
// stage of the caller is known.
if (S.getLangOpts().HLSL) {
if (!S.getLangOpts().HLSLStrictAvailability ||
(DeclEnv != nullptr &&
S.getASTContext().getTargetInfo().getTriple().getEnvironment() ==
llvm::Triple::EnvironmentType::Library))
return false;
}
if (K == AR_Deprecated) {
if (const auto *VD = dyn_cast<VarDecl>(OffendingDecl))
if (VD->isLocalVarDeclOrParm() && VD->isDeprecated())
return true;
}
// Checks if we should emit the availability diagnostic in the context of C.
auto CheckContext = [&](const Decl *C) {
if (K == AR_NotYetIntroduced) {
if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C))
if (AA->getIntroduced() >= DeclVersion &&
AA->getEnvironment() == DeclEnv)
return true;
} else if (K == AR_Deprecated) {
if (C->isDeprecated())
return true;
} else if (K == AR_Unavailable) {
// It is perfectly fine to refer to an 'unavailable' Objective-C method
// when it is referenced from within the @implementation itself. In this
// context, we interpret unavailable as a form of access control.
if (const auto *MD = dyn_cast<ObjCMethodDecl>(OffendingDecl)) {
if (const auto *Impl = dyn_cast<ObjCImplDecl>(C)) {
if (MD->getClassInterface() == Impl->getClassInterface())
return true;
}
}
}
if (C->isUnavailable())
return true;
return false;
};
do {
if (CheckContext(Ctx))
return false;
// An implementation implicitly has the availability of the interface.
// Unless it is "+load" method.
if (const auto *MethodD = dyn_cast<ObjCMethodDecl>(Ctx))
if (MethodD->isClassMethod() &&
MethodD->getSelector().getAsString() == "load")
return true;
if (const auto *CatOrImpl = dyn_cast<ObjCImplDecl>(Ctx)) {
if (const ObjCInterfaceDecl *Interface = CatOrImpl->getClassInterface())
if (CheckContext(Interface))
return false;
}
// A category implicitly has the availability of the interface.
else if (const auto *CatD = dyn_cast<ObjCCategoryDecl>(Ctx))
if (const ObjCInterfaceDecl *Interface = CatD->getClassInterface())
if (CheckContext(Interface))
return false;
} while ((Ctx = cast_or_null<Decl>(Ctx->getDeclContext())));
return true;
}
static unsigned getAvailabilityDiagnosticKind(
const ASTContext &Context, const VersionTuple &DeploymentVersion,
const VersionTuple &DeclVersion, bool HasMatchingEnv) {
const auto &Triple = Context.getTargetInfo().getTriple();
VersionTuple ForceAvailabilityFromVersion;
switch (Triple.getOS()) {
// For iOS, emit the diagnostic even if -Wunguarded-availability is
// not specified for deployment targets >= to iOS 11 or equivalent or
// for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
// later.
case llvm::Triple::IOS:
case llvm::Triple::TvOS:
ForceAvailabilityFromVersion = VersionTuple(/*Major=*/11);
break;
case llvm::Triple::WatchOS:
ForceAvailabilityFromVersion = VersionTuple(/*Major=*/4);
break;
case llvm::Triple::Darwin:
case llvm::Triple::MacOSX:
ForceAvailabilityFromVersion = VersionTuple(/*Major=*/10, /*Minor=*/13);
break;
// For HLSL, use diagnostic from HLSLAvailability group which
// are reported as errors by default and in strict diagnostic mode
// (-fhlsl-strict-availability) and as warnings in relaxed diagnostic
// mode (-Wno-error=hlsl-availability)
case llvm::Triple::ShaderModel:
return HasMatchingEnv ? diag::warn_hlsl_availability
: diag::warn_hlsl_availability_unavailable;
default:
// New Apple targets should always warn about availability.
ForceAvailabilityFromVersion =
(Triple.getVendor() == llvm::Triple::Apple)
? VersionTuple(/*Major=*/0, 0)
: VersionTuple(/*Major=*/(unsigned)-1, (unsigned)-1);
}
if (DeploymentVersion >= ForceAvailabilityFromVersion ||
DeclVersion >= ForceAvailabilityFromVersion)
return HasMatchingEnv ? diag::warn_unguarded_availability_new
: diag::warn_unguarded_availability_unavailable_new;
return HasMatchingEnv ? diag::warn_unguarded_availability
: diag::warn_unguarded_availability_unavailable;
}
static NamedDecl *findEnclosingDeclToAnnotate(Decl *OrigCtx) {
for (Decl *Ctx = OrigCtx; Ctx;
Ctx = cast_or_null<Decl>(Ctx->getDeclContext())) {
if (isa<TagDecl>(Ctx) || isa<FunctionDecl>(Ctx) || isa<ObjCMethodDecl>(Ctx))
return cast<NamedDecl>(Ctx);
if (auto *CD = dyn_cast<ObjCContainerDecl>(Ctx)) {
if (auto *Imp = dyn_cast<ObjCImplDecl>(Ctx))
return Imp->getClassInterface();
return CD;
}
}
return dyn_cast<NamedDecl>(OrigCtx);
}
namespace {
struct AttributeInsertion {
StringRef Prefix;
SourceLocation Loc;
StringRef Suffix;
static AttributeInsertion createInsertionAfter(const NamedDecl *D) {
return {" ", D->getEndLoc(), ""};
}
static AttributeInsertion createInsertionAfter(SourceLocation Loc) {
return {" ", Loc, ""};
}
static AttributeInsertion createInsertionBefore(const NamedDecl *D) {
return {"", D->getBeginLoc(), "\n"};
}
};
} // end anonymous namespace
/// Tries to parse a string as ObjC method name.
///
/// \param Name The string to parse. Expected to originate from availability
/// attribute argument.
/// \param SlotNames The vector that will be populated with slot names. In case
/// of unsuccessful parsing can contain invalid data.
/// \returns A number of method parameters if parsing was successful,
/// std::nullopt otherwise.
static std::optional<unsigned>
tryParseObjCMethodName(StringRef Name, SmallVectorImpl<StringRef> &SlotNames,
const LangOptions &LangOpts) {
// Accept replacements starting with - or + as valid ObjC method names.
if (!Name.empty() && (Name.front() == '-' || Name.front() == '+'))
Name = Name.drop_front(1);
if (Name.empty())
return std::nullopt;
Name.split(SlotNames, ':');
unsigned NumParams;
if (Name.back() == ':') {
// Remove an empty string at the end that doesn't represent any slot.
SlotNames.pop_back();
NumParams = SlotNames.size();
} else {
if (SlotNames.size() != 1)
// Not a valid method name, just a colon-separated string.
return std::nullopt;
NumParams = 0;
}
// Verify all slot names are valid.
bool AllowDollar = LangOpts.DollarIdents;
for (StringRef S : SlotNames) {
if (S.empty())
continue;
if (!isValidAsciiIdentifier(S, AllowDollar))
return std::nullopt;
}
return NumParams;
}
/// Returns a source location in which it's appropriate to insert a new
/// attribute for the given declaration \D.
static std::optional<AttributeInsertion>
createAttributeInsertion(const NamedDecl *D, const SourceManager &SM,
const LangOptions &LangOpts) {
if (isa<ObjCPropertyDecl>(D))
return AttributeInsertion::createInsertionAfter(D);
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (MD->hasBody())
return std::nullopt;
return AttributeInsertion::createInsertionAfter(D);
}
if (const auto *TD = dyn_cast<TagDecl>(D)) {
SourceLocation Loc =
Lexer::getLocForEndOfToken(TD->getInnerLocStart(), 0, SM, LangOpts);
if (Loc.isInvalid())
return std::nullopt;
// Insert after the 'struct'/whatever keyword.
return AttributeInsertion::createInsertionAfter(Loc);
}
return AttributeInsertion::createInsertionBefore(D);
}
/// Actually emit an availability diagnostic for a reference to an unavailable
/// decl.
///
/// \param Ctx The context that the reference occurred in
/// \param ReferringDecl The exact declaration that was referenced.
/// \param OffendingDecl A related decl to \c ReferringDecl that has an
/// availability attribute corresponding to \c K attached to it. Note that this
/// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and
/// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl
/// and OffendingDecl is the EnumDecl.
static void 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) {
// Diagnostics for deprecated or unavailable.
unsigned diag, diag_message, diag_fwdclass_message;
unsigned diag_available_here = diag::note_availability_specified_here;
SourceLocation NoteLocation = OffendingDecl->getLocation();
// Matches 'diag::note_property_attribute' options.
unsigned property_note_select;
// Matches diag::note_availability_specified_here.
unsigned available_here_select_kind;
VersionTuple DeclVersion;
const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl);
const IdentifierInfo *IIEnv = nullptr;
if (AA) {
DeclVersion = AA->getIntroduced();
IIEnv = AA->getEnvironment();
}
if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, IIEnv, Ctx,
OffendingDecl))
return;
SourceLocation Loc = Locs.front();
// The declaration can have multiple availability attributes, we are looking
// at one of them.
if (AA && AA->isInherited()) {
for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl;
Redecl = Redecl->getPreviousDecl()) {
const AvailabilityAttr *AForRedecl =
getAttrForPlatform(S.Context, Redecl);
if (AForRedecl && !AForRedecl->isInherited()) {
// If D is a declaration with inherited attributes, the note should
// point to the declaration with actual attributes.
NoteLocation = Redecl->getLocation();
break;
}
}
}
switch (K) {
case AR_NotYetIntroduced: {
// We would like to emit the diagnostic even if -Wunguarded-availability is
// not specified for deployment targets >= to iOS 11 or equivalent or
// for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
// later.
assert(AA != nullptr && "expecting valid availability attribute");
VersionTuple Introduced = AA->getIntroduced();
bool EnvironmentMatchesOrNone =
hasMatchingEnvironmentOrNone(S.getASTContext(), AA);
const TargetInfo &TI = S.getASTContext().getTargetInfo();
std::string PlatformName(
AvailabilityAttr::getPrettyPlatformName(TI.getPlatformName()));
llvm::StringRef TargetEnvironment(
llvm::Triple::getEnvironmentTypeName(TI.getTriple().getEnvironment()));
llvm::StringRef AttrEnvironment =
AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
bool UseEnvironment =
(!AttrEnvironment.empty() && !TargetEnvironment.empty());
unsigned DiagKind = getAvailabilityDiagnosticKind(
S.Context, S.Context.getTargetInfo().getPlatformMinVersion(),
Introduced, EnvironmentMatchesOrNone);
S.Diag(Loc, DiagKind) << OffendingDecl << PlatformName
<< Introduced.getAsString() << UseEnvironment
<< TargetEnvironment;
S.Diag(OffendingDecl->getLocation(),
diag::note_partial_availability_specified_here)
<< OffendingDecl << PlatformName << Introduced.getAsString()
<< S.Context.getTargetInfo().getPlatformMinVersion().getAsString()
<< UseEnvironment << AttrEnvironment << TargetEnvironment;
// Do not offer to silence the warning or fixits for HLSL
if (S.getLangOpts().HLSL)
return;
if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) {
if (const auto *TD = dyn_cast<TagDecl>(Enclosing))
if (TD->getDeclName().isEmpty()) {
S.Diag(TD->getLocation(),
diag::note_decl_unguarded_availability_silence)
<< /*Anonymous*/ 1 << TD->getKindName();
return;
}
auto FixitNoteDiag =
S.Diag(Enclosing->getLocation(),
diag::note_decl_unguarded_availability_silence)
<< /*Named*/ 0 << Enclosing;
// Don't offer a fixit for declarations with availability attributes.
if (Enclosing->hasAttr<AvailabilityAttr>())
return;
Preprocessor &PP = S.getPreprocessor();
if (!PP.isMacroDefined("API_AVAILABLE"))
return;
std::optional<AttributeInsertion> Insertion = createAttributeInsertion(
Enclosing, S.getSourceManager(), S.getLangOpts());
if (!Insertion)
return;
StringRef PlatformName =
S.getASTContext().getTargetInfo().getPlatformName();
// Apple's API_AVAILABLE macro expands roughly like this.
// API_AVAILABLE(ios(17.0))
// __attribute__((availability(__API_AVAILABLE_PLATFORM_ios(17.0)))
// __attribute__((availability(ios,introduced=17.0)))
// In order to figure out which platform name to use in the API_AVAILABLE
// macro, the associated __API_AVAILABLE_PLATFORM_ macro needs to be
// found. The __API_AVAILABLE_PLATFORM_ macros aren't consistent about
// using the canonical platform name, source spelling name, or one of the
// other supported names (i.e. one of the keys in canonicalizePlatformName
// that's neither). Check all of the supported names for a match.
std::vector<StringRef> EquivalentPlatforms =
AvailabilityAttr::equivalentPlatformNames(PlatformName);
llvm::Twine MacroPrefix = "__API_AVAILABLE_PLATFORM_";
auto AvailablePlatform =
llvm::find_if(EquivalentPlatforms, [&](StringRef EquivalentPlatform) {
return PP.isMacroDefined((MacroPrefix + EquivalentPlatform).str());
});
if (AvailablePlatform == EquivalentPlatforms.end())
return;
std::string Introduced =
OffendingDecl->getVersionIntroduced().getAsString();
FixitNoteDiag << FixItHint::CreateInsertion(
Insertion->Loc,
(llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" +
*AvailablePlatform + "(" + Introduced + "))" + Insertion->Suffix)
.str());
}
return;
}
case AR_Deprecated:
diag = !ObjCPropertyAccess ? diag::warn_deprecated
: diag::warn_property_method_deprecated;
diag_message = diag::warn_deprecated_message;
diag_fwdclass_message = diag::warn_deprecated_fwdclass_message;
property_note_select = /* deprecated */ 0;
available_here_select_kind = /* deprecated */ 2;
if (const auto *AL = OffendingDecl->getAttr<DeprecatedAttr>())
NoteLocation = AL->getLocation();
break;
case AR_Unavailable:
diag = !ObjCPropertyAccess ? diag::err_unavailable
: diag::err_property_method_unavailable;
diag_message = diag::err_unavailable_message;
diag_fwdclass_message = diag::warn_unavailable_fwdclass_message;
property_note_select = /* unavailable */ 1;
available_here_select_kind = /* unavailable */ 0;
if (auto AL = OffendingDecl->getAttr<UnavailableAttr>()) {
if (AL->isImplicit() && AL->getImplicitReason()) {
// Most of these failures are due to extra restrictions in ARC;
// reflect that in the primary diagnostic when applicable.
auto flagARCError = [&] {
if (S.getLangOpts().ObjCAutoRefCount &&
S.getSourceManager().isInSystemHeader(
OffendingDecl->getLocation()))
diag = diag::err_unavailable_in_arc;
};
switch (AL->getImplicitReason()) {
case UnavailableAttr::IR_None: break;
case UnavailableAttr::IR_ARCForbiddenType:
flagARCError();
diag_available_here = diag::note_arc_forbidden_type;
break;
case UnavailableAttr::IR_ForbiddenWeak:
if (S.getLangOpts().ObjCWeakRuntime)
diag_available_here = diag::note_arc_weak_disabled;
else
diag_available_here = diag::note_arc_weak_no_runtime;
break;
case UnavailableAttr::IR_ARCForbiddenConversion:
flagARCError();
diag_available_here = diag::note_performs_forbidden_arc_conversion;
break;
case UnavailableAttr::IR_ARCInitReturnsUnrelated:
flagARCError();
diag_available_here = diag::note_arc_init_returns_unrelated;
break;
case UnavailableAttr::IR_ARCFieldWithOwnership:
flagARCError();
diag_available_here = diag::note_arc_field_with_ownership;
break;
}
}
}
break;
case AR_Available:
llvm_unreachable("Warning for availability of available declaration?");
}
SmallVector<FixItHint, 12> FixIts;
if (K == AR_Deprecated) {
StringRef Replacement;
if (auto AL = OffendingDecl->getAttr<DeprecatedAttr>())
Replacement = AL->getReplacement();
if (auto AL = getAttrForPlatform(S.Context, OffendingDecl))
Replacement = AL->getReplacement();
CharSourceRange UseRange;
if (!Replacement.empty())
UseRange =
CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc));
if (UseRange.isValid()) {
if (const auto *MethodDecl = dyn_cast<ObjCMethodDecl>(ReferringDecl)) {
Selector Sel = MethodDecl->getSelector();
SmallVector<StringRef, 12> SelectorSlotNames;
std::optional<unsigned> NumParams = tryParseObjCMethodName(
Replacement, SelectorSlotNames, S.getLangOpts());
if (NumParams && *NumParams == Sel.getNumArgs()) {
assert(SelectorSlotNames.size() == Locs.size());
for (unsigned I = 0; I < Locs.size(); ++I) {
if (!Sel.getNameForSlot(I).empty()) {
CharSourceRange NameRange = CharSourceRange::getCharRange(
Locs[I], S.getLocForEndOfToken(Locs[I]));
FixIts.push_back(FixItHint::CreateReplacement(
NameRange, SelectorSlotNames[I]));
} else
FixIts.push_back(
FixItHint::CreateInsertion(Locs[I], SelectorSlotNames[I]));
}
} else
FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
} else
FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
}
}
// We emit deprecation warning for deprecated specializations
// when their instantiation stacks originate outside
// of a system header, even if the diagnostics is suppresed at the
// point of definition.
SourceLocation InstantiationLoc =
S.getTopMostPointOfInstantiation(ReferringDecl);
bool ShouldAllowWarningInSystemHeader =
InstantiationLoc != Loc &&
!S.getSourceManager().isInSystemHeader(InstantiationLoc);
struct AllowWarningInSystemHeaders {
AllowWarningInSystemHeaders(DiagnosticsEngine &E,
bool AllowWarningInSystemHeaders)
: Engine(E), Prev(E.getSuppressSystemWarnings()) {
E.setSuppressSystemWarnings(!AllowWarningInSystemHeaders);
}
~AllowWarningInSystemHeaders() { Engine.setSuppressSystemWarnings(Prev); }
private:
DiagnosticsEngine &Engine;
bool Prev;
} SystemWarningOverrideRAII(S.getDiagnostics(),
ShouldAllowWarningInSystemHeader);
if (!Message.empty()) {
S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts;
if (ObjCProperty)
S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
<< ObjCProperty->getDeclName() << property_note_select;
} else if (!UnknownObjCClass) {
S.Diag(Loc, diag) << ReferringDecl << FixIts;
if (ObjCProperty)
S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
<< ObjCProperty->getDeclName() << property_note_select;
} else {
S.Diag(Loc, diag_fwdclass_message) << ReferringDecl << FixIts;
S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class);
}
S.Diag(NoteLocation, diag_available_here)
<< OffendingDecl << available_here_select_kind;
}
void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) {
assert(DD.Kind == DelayedDiagnostic::Availability &&
"Expected an availability diagnostic here");
DD.Triggered = true;
DoEmitAvailabilityWarning(
*this, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(),
DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(),
DD.getAvailabilitySelectorLocs(), DD.getUnknownObjCClass(),
DD.getObjCProperty(), false);
}
static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR,
const NamedDecl *ReferringDecl,
const NamedDecl *OffendingDecl,
StringRef Message,
ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
const ObjCPropertyDecl *ObjCProperty,
bool ObjCPropertyAccess) {
// Delay if we're currently parsing a declaration.
if (S.DelayedDiagnostics.shouldDelayDiagnostics()) {
S.DelayedDiagnostics.add(
DelayedDiagnostic::makeAvailability(
AR, Locs, ReferringDecl, OffendingDecl, UnknownObjCClass,
ObjCProperty, Message, ObjCPropertyAccess));
return;
}
Decl *Ctx = cast<Decl>(S.getCurLexicalContext());
DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl,
Message, Locs, UnknownObjCClass, ObjCProperty,
ObjCPropertyAccess);
}
namespace {
/// Returns true if the given statement can be a body-like child of \p Parent.
bool isBodyLikeChildStmt(const Stmt *S, const Stmt *Parent) {
switch (Parent->getStmtClass()) {
case Stmt::IfStmtClass:
return cast<IfStmt>(Parent)->getThen() == S ||
cast<IfStmt>(Parent)->getElse() == S;
case Stmt::WhileStmtClass:
return cast<WhileStmt>(Parent)->getBody() == S;
case Stmt::DoStmtClass:
return cast<DoStmt>(Parent)->getBody() == S;
case Stmt::ForStmtClass:
return cast<ForStmt>(Parent)->getBody() == S;
case Stmt::CXXForRangeStmtClass:
return cast<CXXForRangeStmt>(Parent)->getBody() == S;
case Stmt::ObjCForCollectionStmtClass:
return cast<ObjCForCollectionStmt>(Parent)->getBody() == S;
case Stmt::CaseStmtClass:
case Stmt::DefaultStmtClass:
return cast<SwitchCase>(Parent)->getSubStmt() == S;
default:
return false;
}
}
class StmtUSEFinder : public DynamicRecursiveASTVisitor {
const Stmt *Target;
public:
bool VisitStmt(Stmt *S) override { return S != Target; }
/// Returns true if the given statement is present in the given declaration.
static bool isContained(const Stmt *Target, const Decl *D) {
StmtUSEFinder Visitor;
Visitor.Target = Target;
return !Visitor.TraverseDecl(const_cast<Decl *>(D));
}
};
/// Traverses the AST and finds the last statement that used a given
/// declaration.
class LastDeclUSEFinder : public DynamicRecursiveASTVisitor {
const Decl *D;
public:
bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
if (DRE->getDecl() == D)
return false;
return true;
}
static const Stmt *findLastStmtThatUsesDecl(const Decl *D,
const CompoundStmt *Scope) {
LastDeclUSEFinder Visitor;
Visitor.D = D;
for (const Stmt *S : llvm::reverse(Scope->body())) {
if (!Visitor.TraverseStmt(const_cast<Stmt *>(S)))
return S;
}
return nullptr;
}
};
/// This class implements -Wunguarded-availability.
///
/// This is done with a traversal of the AST of a function that makes reference
/// to a partially available declaration. Whenever we encounter an \c if of the
/// form: \c if(@available(...)), we use the version from the condition to visit
/// the then statement.
class DiagnoseUnguardedAvailability : public DynamicRecursiveASTVisitor {
Sema &SemaRef;
Decl *Ctx;
/// Stack of potentially nested 'if (@available(...))'s.
SmallVector<VersionTuple, 8> AvailabilityStack;
SmallVector<const Stmt *, 16> StmtStack;
void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range,
ObjCInterfaceDecl *ClassReceiver = nullptr);
public:
DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx)
: SemaRef(SemaRef), Ctx(Ctx) {
AvailabilityStack.push_back(
SemaRef.Context.getTargetInfo().getPlatformMinVersion());
}
bool TraverseStmt(Stmt *S) override {
if (!S)
return true;
StmtStack.push_back(S);
bool Result = DynamicRecursiveASTVisitor::TraverseStmt(S);
StmtStack.pop_back();
return Result;
}
void IssueDiagnostics(Stmt *S) { TraverseStmt(S); }
bool TraverseIfStmt(IfStmt *If) override;
// for 'case X:' statements, don't bother looking at the 'X'; it can't lead
// to any useful diagnostics.
bool TraverseCaseStmt(CaseStmt *CS) override {
return TraverseStmt(CS->getSubStmt());
}
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) override {
if (ObjCMethodDecl *D = Msg->getMethodDecl()) {
ObjCInterfaceDecl *ID = nullptr;
QualType ReceiverTy = Msg->getClassReceiver();
if (!ReceiverTy.isNull() && ReceiverTy->getAsObjCInterfaceType())
ID = ReceiverTy->getAsObjCInterfaceType()->getInterface();
DiagnoseDeclAvailability(
D, SourceRange(Msg->getSelectorStartLoc(), Msg->getEndLoc()), ID);
}
return true;
}
bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
DiagnoseDeclAvailability(DRE->getDecl(),
SourceRange(DRE->getBeginLoc(), DRE->getEndLoc()));
return true;
}
bool VisitMemberExpr(MemberExpr *ME) override {
DiagnoseDeclAvailability(ME->getMemberDecl(),
SourceRange(ME->getBeginLoc(), ME->getEndLoc()));
return true;
}
bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) override {
SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use)
<< (!SemaRef.getLangOpts().ObjC);
return true;
}
bool VisitTypeLoc(TypeLoc Ty) override;
};
void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) {
AvailabilityResult Result;
const NamedDecl *OffendingDecl;
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(SemaRef, D, nullptr, ReceiverClass);
if (Result != AR_Available) {
// All other diagnostic kinds have already been handled in
// DiagnoseAvailabilityOfDecl.
if (Result != AR_NotYetIntroduced)
return;
const AvailabilityAttr *AA =
getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl);
assert(AA != nullptr && "expecting valid availability attribute");
bool EnvironmentMatchesOrNone =
hasMatchingEnvironmentOrNone(SemaRef.getASTContext(), AA);
VersionTuple Introduced = AA->getIntroduced();
if (EnvironmentMatchesOrNone && AvailabilityStack.back() >= Introduced)
return;
// If the context of this function is less available than D, we should not
// emit a diagnostic.
if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced,
AA->getEnvironment(), Ctx,
OffendingDecl))
return;
const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
std::string PlatformName(
AvailabilityAttr::getPrettyPlatformName(TI.getPlatformName()));
llvm::StringRef TargetEnvironment(TI.getTriple().getEnvironmentName());
llvm::StringRef AttrEnvironment =
AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
bool UseEnvironment =
(!AttrEnvironment.empty() && !TargetEnvironment.empty());
unsigned DiagKind = getAvailabilityDiagnosticKind(
SemaRef.Context,
SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced,
EnvironmentMatchesOrNone);
SemaRef.Diag(Range.getBegin(), DiagKind)
<< Range << D << PlatformName << Introduced.getAsString()
<< UseEnvironment << TargetEnvironment;
SemaRef.Diag(OffendingDecl->getLocation(),
diag::note_partial_availability_specified_here)
<< OffendingDecl << PlatformName << Introduced.getAsString()
<< SemaRef.Context.getTargetInfo().getPlatformMinVersion().getAsString()
<< UseEnvironment << AttrEnvironment << TargetEnvironment;
// Do not offer to silence the warning or fixits for HLSL
if (SemaRef.getLangOpts().HLSL)
return;
auto FixitDiag =
SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
<< Range << D
<< (SemaRef.getLangOpts().ObjC ? /*@available*/ 0
: /*__builtin_available*/ 1);
// Find the statement which should be enclosed in the if @available check.
if (StmtStack.empty())
return;
const Stmt *StmtOfUse = StmtStack.back();
const CompoundStmt *Scope = nullptr;
for (const Stmt *S : llvm::reverse(StmtStack)) {
if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
Scope = CS;
break;
}
if (isBodyLikeChildStmt(StmtOfUse, S)) {
// The declaration won't be seen outside of the statement, so we don't
// have to wrap the uses of any declared variables in if (@available).
// Therefore we can avoid setting Scope here.
break;
}
StmtOfUse = S;
}
const Stmt *LastStmtOfUse = nullptr;
if (isa<DeclStmt>(StmtOfUse) && Scope) {
for (const Decl *D : cast<DeclStmt>(StmtOfUse)->decls()) {
if (StmtUSEFinder::isContained(StmtStack.back(), D)) {
LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope);
break;
}
}
}
const SourceManager &SM = SemaRef.getSourceManager();
SourceLocation IfInsertionLoc =
SM.getExpansionLoc(StmtOfUse->getBeginLoc());
SourceLocation StmtEndLoc =
SM.getExpansionRange(
(LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc())
.getEnd();
if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc))
return;
StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM);
const char *ExtraIndentation = " ";
std::string FixItString;
llvm::raw_string_ostream FixItOS(FixItString);
FixItOS << "if (" << (SemaRef.getLangOpts().ObjC ? "@available"
: "__builtin_available")
<< "("
<< AvailabilityAttr::getPlatformNameSourceSpelling(
SemaRef.getASTContext().getTargetInfo().getPlatformName())
<< " " << Introduced.getAsString() << ", *)) {\n"
<< Indentation << ExtraIndentation;
FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str());
SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken(
StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(),
/*SkipTrailingWhitespaceAndNewLine=*/false);
if (ElseInsertionLoc.isInvalid())
ElseInsertionLoc =
Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts());
FixItOS.str().clear();
FixItOS << "\n"
<< Indentation << "} else {\n"
<< Indentation << ExtraIndentation
<< "// Fallback on earlier versions\n"
<< Indentation << "}";
FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str());
}
}
bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
const Type *TyPtr = Ty.getTypePtr();
SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()};
if (Range.isInvalid())
return true;
if (const auto *TT = dyn_cast<TagType>(TyPtr)) {
TagDecl *TD = TT->getDecl();
DiagnoseDeclAvailability(TD, Range);
} else if (const auto *TD = dyn_cast<TypedefType>(TyPtr)) {
TypedefNameDecl *D = TD->getDecl();
DiagnoseDeclAvailability(D, Range);
} else if (const auto *ObjCO = dyn_cast<ObjCObjectType>(TyPtr)) {
if (NamedDecl *D = ObjCO->getInterface())
DiagnoseDeclAvailability(D, Range);
}
return true;
}
struct ExtractedAvailabilityExpr {
const ObjCAvailabilityCheckExpr *E = nullptr;
bool isNegated = false;
};
ExtractedAvailabilityExpr extractAvailabilityExpr(const Expr *IfCond) {
const auto *E = IfCond;
bool IsNegated = false;
while (true) {
E = E->IgnoreParens();
if (const auto *AE = dyn_cast<ObjCAvailabilityCheckExpr>(E)) {
return ExtractedAvailabilityExpr{AE, IsNegated};
}
const auto *UO = dyn_cast<UnaryOperator>(E);
if (!UO || UO->getOpcode() != UO_LNot) {
return ExtractedAvailabilityExpr{};
}
E = UO->getSubExpr();
IsNegated = !IsNegated;
}
}
bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
ExtractedAvailabilityExpr IfCond = extractAvailabilityExpr(If->getCond());
if (!IfCond.E) {
// This isn't an availability checking 'if', we can just continue.
return DynamicRecursiveASTVisitor::TraverseIfStmt(If);
}
VersionTuple CondVersion = IfCond.E->getVersion();
// If we're using the '*' case here or if this check is redundant, then we
// use the enclosing version to check both branches.
if (CondVersion.empty() || CondVersion <= AvailabilityStack.back()) {
return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
}
auto *Guarded = If->getThen();
auto *Unguarded = If->getElse();
if (IfCond.isNegated) {
std::swap(Guarded, Unguarded);
}
AvailabilityStack.push_back(CondVersion);
bool ShouldContinue = TraverseStmt(Guarded);
AvailabilityStack.pop_back();
return ShouldContinue && TraverseStmt(Unguarded);
}
} // end anonymous namespace
void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) {
Stmt *Body = nullptr;
if (auto *FD = D->getAsFunction()) {
Body = FD->getBody();
if (auto *CD = dyn_cast<CXXConstructorDecl>(FD))
for (const CXXCtorInitializer *CI : CD->inits())
DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(CI->getInit());
} else if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
Body = MD->getBody();
else if (auto *BD = dyn_cast<BlockDecl>(D))
Body = BD->getBody();
assert(Body && "Need a body here!");
DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(Body);
}
FunctionScopeInfo *Sema::getCurFunctionAvailabilityContext() {
if (FunctionScopes.empty())
return nullptr;
// Conservatively search the entire current function scope context for
// availability violations. This ensures we always correctly analyze nested
// classes, blocks, lambdas, etc. that may or may not be inside if(@available)
// checks themselves.
return FunctionScopes.front();
}
void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
ArrayRef<SourceLocation> Locs,
const ObjCInterfaceDecl *UnknownObjCClass,
bool ObjCPropertyAccess,
bool AvoidPartialAvailabilityChecks,
ObjCInterfaceDecl *ClassReceiver) {
std::string Message;
AvailabilityResult Result;
const NamedDecl* OffendingDecl;
// See if this declaration is unavailable, deprecated, or partial.
std::tie(Result, OffendingDecl) =
ShouldDiagnoseAvailabilityOfDecl(*this, D, &Message, ClassReceiver);
if (Result == AR_Available)
return;
if (Result == AR_NotYetIntroduced) {
if (AvoidPartialAvailabilityChecks)
return;
// We need to know the @available context in the current function to
// diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that
// when we're done parsing the current function.
if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext()) {
Context->HasPotentialAvailabilityViolations = true;
return;
}
}
const ObjCPropertyDecl *ObjCPDecl = nullptr;
if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
AvailabilityResult PDeclResult = PD->getAvailability(nullptr);
if (PDeclResult == Result)
ObjCPDecl = PD;
}
}
EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs,
UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess);
}