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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1149 lines
43 KiB
C++
Raw Normal View History

//===--- 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);
}
[Sema] Always search the full function scope context if a potential availability violation is encountered This fixes both https://bugs.llvm.org/show_bug.cgi?id=50309 and https://bugs.llvm.org/show_bug.cgi?id=50310. Previously, lambdas inside functions would mark their own bodies for later analysis when encountering a potentially unavailable decl, without taking into consideration that the entire lambda itself might be correctly guarded inside an @available check. The same applied to inner class member functions. Blocks happened to work as expected already, since Sema::getEnclosingFunction() skips through block scopes. This patch instead simply and conservatively marks the entire outermost function scope for search, and removes some special-case logic that prevented DiagnoseUnguardedAvailabilityViolations from traversing down into lambdas and nested functions. This correctly accounts for arbitrarily nested lambdas, inner classes, and blocks that may be inside appropriate @available checks at any ancestor level. It also treats all potential availability violations inside functions consistently, without being overly sensitive to the current DeclContext, which previously caused issues where e.g. nested struct members were warned about twice. DiagnoseUnguardedAvailabilityViolations now has more work to do in some cases, particularly in functions with many (possibly deeply) nested lambdas and classes, but the big-O is the same, and the simplicity of the approach and the fact that it fixes at least two bugs feels like a strong win. Differential Revision: https://reviews.llvm.org/D102338
2021-05-24 21:13:30 -07:00
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.
[Sema] Always search the full function scope context if a potential availability violation is encountered This fixes both https://bugs.llvm.org/show_bug.cgi?id=50309 and https://bugs.llvm.org/show_bug.cgi?id=50310. Previously, lambdas inside functions would mark their own bodies for later analysis when encountering a potentially unavailable decl, without taking into consideration that the entire lambda itself might be correctly guarded inside an @available check. The same applied to inner class member functions. Blocks happened to work as expected already, since Sema::getEnclosingFunction() skips through block scopes. This patch instead simply and conservatively marks the entire outermost function scope for search, and removes some special-case logic that prevented DiagnoseUnguardedAvailabilityViolations from traversing down into lambdas and nested functions. This correctly accounts for arbitrarily nested lambdas, inner classes, and blocks that may be inside appropriate @available checks at any ancestor level. It also treats all potential availability violations inside functions consistently, without being overly sensitive to the current DeclContext, which previously caused issues where e.g. nested struct members were warned about twice. DiagnoseUnguardedAvailabilityViolations now has more work to do in some cases, particularly in functions with many (possibly deeply) nested lambdas and classes, but the big-O is the same, and the simplicity of the approach and the fact that it fixes at least two bugs feels like a strong win. Differential Revision: https://reviews.llvm.org/D102338
2021-05-24 21:13:30 -07:00
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);
}