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

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

959 lines
35 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/RecursiveASTVisitor.h"
#include "clang/Basic/DiagnosticSema.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"
using namespace clang;
using namespace sema;
static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
const Decl *D) {
// Check each AvailabilityAttr to find the one for this platform.
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)
return Avail;
}
}
return nullptr;
}
/// 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) {
if (const auto *TT = TD->getUnderlyingType()->getAs<TagType>()) {
D = TT->getDecl();
Result = D->getAvailability(Message);
continue;
}
}
break;
}
// 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.NSAPIObj && ClassReceiver) {
ObjCMethodDecl *Init = ClassReceiver->lookupInstanceMethod(
S.NSAPIObj->getInitSelector());
if (Init && Result == AR_Available && MD->isClassMethod() &&
MD->getSelector() == S.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, Decl *Ctx,
const NamedDecl *OffendingDecl) {
assert(K != AR_Available && "Expected an unavailable declaration here!");
// 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)
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 bool
shouldDiagnoseAvailabilityByDefault(const ASTContext &Context,
const VersionTuple &DeploymentVersion,
const VersionTuple &DeclVersion) {
const auto &Triple = Context.getTargetInfo().getTriple();
VersionTuple ForceAvailabilityFromVersion;
switch (Triple.getOS()) {
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;
default:
// New targets should always warn about availability.
return Triple.getVendor() == llvm::Triple::Apple;
}
return DeploymentVersion >= ForceAvailabilityFromVersion ||
DeclVersion >= ForceAvailabilityFromVersion;
}
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, None
/// otherwise.
static 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 None;
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 None;
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 None;
}
return NumParams;
}
/// Returns a source location in which it's appropriate to insert a new
/// attribute for the given declaration \D.
static 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 None;
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 None;
// 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;
if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl))
DeclVersion = AA->getIntroduced();
if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, Ctx,
OffendingDecl))
return;
SourceLocation Loc = Locs.front();
// The declaration can have multiple availability attributes, we are looking
// at one of them.
const AvailabilityAttr *A = getAttrForPlatform(S.Context, OffendingDecl);
if (A && A->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.
const AvailabilityAttr *AA =
getAttrForPlatform(S.getASTContext(), OffendingDecl);
VersionTuple Introduced = AA->getIntroduced();
bool UseNewWarning = shouldDiagnoseAvailabilityByDefault(
S.Context, S.Context.getTargetInfo().getPlatformMinVersion(),
Introduced);
unsigned Warning = UseNewWarning ? diag::warn_unguarded_availability_new
: diag::warn_unguarded_availability;
std::string PlatformName(AvailabilityAttr::getPrettyPlatformName(
S.getASTContext().getTargetInfo().getPlatformName()));
S.Diag(Loc, Warning) << OffendingDecl << PlatformName
<< Introduced.getAsString();
S.Diag(OffendingDecl->getLocation(),
diag::note_partial_availability_specified_here)
<< OffendingDecl << PlatformName << Introduced.getAsString()
<< S.Context.getTargetInfo().getPlatformMinVersion().getAsString();
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;
if (!S.getPreprocessor().isMacroDefined("API_AVAILABLE"))
return;
Optional<AttributeInsertion> Insertion = createAttributeInsertion(
Enclosing, S.getSourceManager(), S.getLangOpts());
if (!Insertion)
return;
std::string PlatformName =
AvailabilityAttr::getPlatformNameSourceSpelling(
S.getASTContext().getTargetInfo().getPlatformName())
.lower();
std::string Introduced =
OffendingDecl->getVersionIntroduced().getAsString();
FixitNoteDiag << FixItHint::CreateInsertion(
Insertion->Loc,
(llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" + PlatformName +
"(" + 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;
Optional<unsigned> NumParams = tryParseObjCMethodName(
Replacement, SelectorSlotNames, S.getLangOpts());
if (NumParams && NumParams.getValue() == 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));
}
}
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 RecursiveASTVisitor<StmtUSEFinder> {
const Stmt *Target;
public:
bool VisitStmt(Stmt *S) { 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 RecursiveASTVisitor<LastDeclUSEFinder> {
const Decl *D;
public:
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
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 RecursiveASTVisitor<DiagnoseUnguardedAvailability> {
typedef RecursiveASTVisitor<DiagnoseUnguardedAvailability> Base;
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) {
if (!S)
return true;
StmtStack.push_back(S);
bool Result = Base::TraverseStmt(S);
StmtStack.pop_back();
return Result;
}
void IssueDiagnostics(Stmt *S) { TraverseStmt(S); }
bool TraverseIfStmt(IfStmt *If);
// for 'case X:' statements, don't bother looking at the 'X'; it can't lead
// to any useful diagnostics.
bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); }
bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { return true; }
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
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) {
DiagnoseDeclAvailability(DRE->getDecl(),
SourceRange(DRE->getBeginLoc(), DRE->getEndLoc()));
return true;
}
bool VisitMemberExpr(MemberExpr *ME) {
DiagnoseDeclAvailability(ME->getMemberDecl(),
SourceRange(ME->getBeginLoc(), ME->getEndLoc()));
return true;
}
bool VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) {
SemaRef.Diag(E->getBeginLoc(), diag::warn_at_available_unchecked_use)
<< (!SemaRef.getLangOpts().ObjC);
return true;
}
bool VisitTypeLoc(TypeLoc Ty);
};
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);
VersionTuple Introduced = AA->getIntroduced();
if (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, Ctx,
OffendingDecl))
return;
// 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.
unsigned DiagKind =
shouldDiagnoseAvailabilityByDefault(
SemaRef.Context,
SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced)
? diag::warn_unguarded_availability_new
: diag::warn_unguarded_availability;
std::string PlatformName(AvailabilityAttr::getPrettyPlatformName(
SemaRef.getASTContext().getTargetInfo().getPlatformName()));
SemaRef.Diag(Range.getBegin(), DiagKind)
<< Range << D << PlatformName << Introduced.getAsString();
SemaRef.Diag(OffendingDecl->getLocation(),
diag::note_partial_availability_specified_here)
<< OffendingDecl << PlatformName << Introduced.getAsString()
<< SemaRef.Context.getTargetInfo()
.getPlatformMinVersion()
.getAsString();
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;
}
bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
VersionTuple CondVersion;
if (auto *E = dyn_cast<ObjCAvailabilityCheckExpr>(If->getCond())) {
CondVersion = 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());
} else {
// This isn't an availability checking 'if', we can just continue.
return Base::TraverseIfStmt(If);
}
AvailabilityStack.push_back(CondVersion);
bool ShouldContinue = TraverseStmt(If->getThen());
AvailabilityStack.pop_back();
return ShouldContinue && TraverseStmt(If->getElse());
}
} // end anonymous namespace
void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) {
Stmt *Body = nullptr;
if (auto *FD = D->getAsFunction()) {
// FIXME: We only examine the pattern decl for availability violations now,
// but we should also examine instantiated templates.
if (FD->isTemplateInstantiation())
return;
Body = FD->getBody();
} 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-25 12:13:30 +08: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-25 12:13:30 +08: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);
}