Skip to content

Commit

Permalink
[ObjC] Warn on unguarded use of partial declaration
Browse files Browse the repository at this point in the history
This commit adds a traversal of the AST after Sema of a function that diagnoses
unguarded references to declarations that are partially available (based on
availability attributes). This traversal is only done when we would otherwise
emit -Wpartial-availability.

This commit is part of a feature I proposed here:
http://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html

Differential revision: https://reviews.llvm.org/D23003

llvm-svn: 278826
  • Loading branch information
epilk committed Aug 16, 2016
1 parent c98ef71 commit 5cd5717
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 49 deletions.
2 changes: 2 additions & 0 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,8 @@ class IfStmt : public Stmt {
bool isConstexpr() const { return IfStmtBits.IsConstexpr; }
void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; }

bool isObjCAvailabilityCheck() const;

SourceLocation getLocStart() const LLVM_READONLY { return IfLoc; }
SourceLocation getLocEnd() const LLVM_READONLY {
if (SubExprs[ELSE])
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ def CXX11CompatDeprecatedWritableStr :
def DeprecatedAttributes : DiagGroup<"deprecated-attributes">;
def DeprecatedDeclarations : DiagGroup<"deprecated-declarations">;
def UnavailableDeclarations : DiagGroup<"unavailable-declarations">;
def PartialAvailability : DiagGroup<"partial-availability">;
def UnguardedAvailability : DiagGroup<"unguarded-availability">;
// partial-availability is an alias of unguarded-availability.
def : DiagGroup<"partial-availability", [UnguardedAvailability]>;
def DeprecatedImplementations :DiagGroup<"deprecated-implementations">;
def DeprecatedIncrementBool : DiagGroup<"deprecated-increment-bool">;
def DeprecatedRegister : DiagGroup<"deprecated-register">;
Expand Down
27 changes: 16 additions & 11 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2656,8 +2656,20 @@ def note_overridden_method : Note<
def note_protocol_method : Note<
"protocol method is here">;

def warn_available_using_star_case : Warning<
"using '*' case here, platform %0 is not accounted for">, InGroup<UnguardedAvailability>;
def warn_unguarded_availability :
Warning<"%0 is only available on %1 %2 or newer">,
InGroup<UnguardedAvailability>, DefaultIgnore;
def warn_partial_availability : Warning<"%0 is only available conditionally">,
InGroup<UnguardedAvailability>, DefaultIgnore;
def note_partial_availability_silence : Note<
"explicitly redeclare %0 to silence this warning">;
def note_unguarded_available_silence : Note<
"enclose %0 in an @available check to silence this warning">;
def warn_partial_message : Warning<"%0 is partial: %1">,
InGroup<UnguardedAvailability>, DefaultIgnore;
def warn_partial_fwdclass_message : Warning<
"%0 may be partial because the receiver type is unknown">,
InGroup<UnguardedAvailability>, DefaultIgnore;

// Thread Safety Attributes
def warn_invalid_capability_name : Warning<
Expand Down Expand Up @@ -4279,15 +4291,6 @@ def err_not_found_by_two_phase_lookup : Error<"call to function %0 that is neith
def note_not_found_by_two_phase_lookup : Note<"%0 should be declared prior to the "
"call site%select{| or in %2| or in an associated namespace of one of its arguments}1">;
def err_undeclared_use : Error<"use of undeclared %0">;
def warn_partial_availability : Warning<"%0 is only available conditionally">,
InGroup<PartialAvailability>, DefaultIgnore;
def note_partial_availability_silence : Note<
"explicitly redeclare %0 to silence this warning">;
def warn_partial_message : Warning<"%0 is partial: %1">,
InGroup<PartialAvailability>, DefaultIgnore;
def warn_partial_fwdclass_message : Warning<
"%0 may be partial because the receiver type is unknown">,
InGroup<PartialAvailability>, DefaultIgnore;
def warn_deprecated : Warning<"%0 is deprecated">,
InGroup<DeprecatedDeclarations>;
def warn_property_method_deprecated :
Expand Down Expand Up @@ -4738,6 +4741,8 @@ def note_protected_by_vla_type_alias : Note<
"jump bypasses initialization of VLA type alias">;
def note_protected_by_constexpr_if : Note<
"jump enters controlled statement of constexpr if">;
def note_protected_by_if_available : Note<
"jump enters controlled statement of if available">;
def note_protected_by_vla : Note<
"jump bypasses initialization of variable length array">;
def note_protected_by_objc_try : Note<
Expand Down
7 changes: 6 additions & 1 deletion clang/include/clang/Sema/ScopeInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,15 @@ class FunctionScopeInfo {
bool HasDroppedStmt : 1;

/// \brief True if current scope is for OpenMP declare reduction combiner.
bool HasOMPDeclareReductionCombiner;
bool HasOMPDeclareReductionCombiner : 1;

/// \brief Whether there is a fallthrough statement in this function.
bool HasFallthroughStmt : 1;

/// \brief Whether we make reference to a declaration that could be
/// unavailable.
bool HasPotentialAvailabilityViolations : 1;

/// A flag that is set when parsing a method that must call super's
/// implementation, such as \c -dealloc, \c -finalize, or any method marked
/// with \c __attribute__((objc_requires_super)).
Expand Down Expand Up @@ -381,6 +385,7 @@ class FunctionScopeInfo {
HasDroppedStmt(false),
HasOMPDeclareReductionCombiner(false),
HasFallthroughStmt(false),
HasPotentialAvailabilityViolations(false),
ObjCShouldCallSuper(false),
ObjCIsDesignatedInit(false),
ObjCWarnForNoDesignatedInitChain(false),
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3639,6 +3639,9 @@ class Sema {
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);

/// \brief Issue any -Wunguarded-availability warnings in \c FD
void DiagnoseUnguardedAvailabilityViolations(Decl *FD);

//===--------------------------------------------------------------------===//
// Expression Parsing Callbacks: SemaExpr.cpp.

Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/Stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,10 @@ void IfStmt::setConditionVariable(const ASTContext &C, VarDecl *V) {
VarRange.getEnd());
}

bool IfStmt::isObjCAvailabilityCheck() const {
return isa<ObjCAvailabilityCheckExpr>(SubExprs[COND]);
}

ForStmt::ForStmt(const ASTContext &C, Stmt *Init, Expr *Cond, VarDecl *condVar,
Expr *Inc, Stmt *Body, SourceLocation FL, SourceLocation LP,
SourceLocation RP)
Expand Down
17 changes: 7 additions & 10 deletions clang/lib/Sema/JumpDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,30 +325,27 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,

case Stmt::IfStmtClass: {
IfStmt *IS = cast<IfStmt>(S);
if (!IS->isConstexpr())
if (!(IS->isConstexpr() || IS->isObjCAvailabilityCheck()))
break;

unsigned Diag = IS->isConstexpr() ? diag::note_protected_by_constexpr_if
: diag::note_protected_by_if_available;

if (VarDecl *Var = IS->getConditionVariable())
BuildScopeInformation(Var, ParentScope);

// Cannot jump into the middle of the condition.
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_constexpr_if, 0,
IS->getLocStart()));
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
BuildScopeInformation(IS->getCond(), NewParentScope);

// Jumps into either arm of an 'if constexpr' are not allowed.
NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_constexpr_if, 0,
IS->getLocStart()));
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
BuildScopeInformation(IS->getThen(), NewParentScope);
if (Stmt *Else = IS->getElse()) {
NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_constexpr_if, 0,
IS->getLocStart()));
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
BuildScopeInformation(Else, NewParentScope);
}
return;
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/ScopeInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ void FunctionScopeInfo::Clear() {
HasIndirectGoto = false;
HasDroppedStmt = false;
HasOMPDeclareReductionCombiner = false;
HasPotentialAvailabilityViolations = false;
ObjCShouldCallSuper = false;
ObjCIsDesignatedInit = false;
ObjCWarnForNoDesignatedInitChain = false;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11736,6 +11736,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
return nullptr;
}

if (Body && getCurFunction()->HasPotentialAvailabilityViolations)
DiagnoseUnguardedAvailabilityViolations(dcl);

assert(!getCurFunction()->ObjCShouldCallSuper &&
"This should only be set for ObjC methods, which should have been "
"handled in the block above.");
Expand Down
147 changes: 147 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
Expand Down Expand Up @@ -6330,6 +6331,9 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
break;

case AR_NotYetIntroduced:
assert(!S.getCurFunctionOrMethodDecl() &&
"Function-level partial availablity should not be diagnosed here!");

diag = diag::warn_partial_availability;
diag_message = diag::warn_partial_message;
diag_fwdclass_message = diag::warn_partial_fwdclass_message;
Expand Down Expand Up @@ -6511,3 +6515,146 @@ VersionTuple Sema::getVersionForDecl(const Decl *D) const {

return std::max(DeclVersion, Context.getTargetInfo().getPlatformMinVersion());
}

namespace {

/// \brief 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;

/// Stack of potentially nested 'if (@available(...))'s.
SmallVector<VersionTuple, 8> AvailabilityStack;

void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range);

public:
DiagnoseUnguardedAvailability(Sema &SemaRef, VersionTuple BaseVersion)
: SemaRef(SemaRef) {
AvailabilityStack.push_back(BaseVersion);
}

void IssueDiagnostics(Stmt *S) { TraverseStmt(S); }

bool TraverseIfStmt(IfStmt *If);

bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
if (ObjCMethodDecl *D = Msg->getMethodDecl())
DiagnoseDeclAvailability(
D, SourceRange(Msg->getSelectorStartLoc(), Msg->getLocEnd()));
return true;
}

bool VisitDeclRefExpr(DeclRefExpr *DRE) {
DiagnoseDeclAvailability(DRE->getDecl(),
SourceRange(DRE->getLocStart(), DRE->getLocEnd()));
return true;
}

bool VisitMemberExpr(MemberExpr *ME) {
DiagnoseDeclAvailability(ME->getMemberDecl(),
SourceRange(ME->getLocStart(), ME->getLocEnd()));
return true;
}

bool VisitTypeLoc(TypeLoc Ty);
};

void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
NamedDecl *D, SourceRange Range) {

VersionTuple ContextVersion = AvailabilityStack.back();
if (AvailabilityResult Result = SemaRef.ShouldDiagnoseAvailabilityOfDecl(
D, ContextVersion, nullptr)) {
// All other diagnostic kinds have already been handled in
// DiagnoseAvailabilityOfDecl.
if (Result != AR_NotYetIntroduced)
return;

const AvailabilityAttr *AA = getAttrForPlatform(SemaRef.getASTContext(), D);
VersionTuple Introduced = AA->getIntroduced();

SemaRef.Diag(Range.getBegin(), diag::warn_unguarded_availability)
<< Range << D
<< AvailabilityAttr::getPrettyPlatformName(
SemaRef.getASTContext().getTargetInfo().getPlatformName())
<< Introduced.getAsString();

SemaRef.Diag(D->getLocation(), diag::note_availability_specified_here)
<< D << /* partial */ 3;

// FIXME: Replace this with a fixit diagnostic.
SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
<< Range << D;
}
}

bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
const Type *TyPtr = Ty.getTypePtr();
SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()};

if (const TagType *TT = dyn_cast<TagType>(TyPtr)) {
TagDecl *TD = TT->getDecl();
DiagnoseDeclAvailability(TD, Range);

} else if (const TypedefType *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 Base::TraverseStmt(If->getThen()) &&
Base::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();

assert(Body && "Need a body here!");

VersionTuple BaseVersion = getVersionForDecl(D);
DiagnoseUnguardedAvailability(*this, BaseVersion).IssueDiagnostics(Body);
}
10 changes: 5 additions & 5 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ DiagnoseAvailabilityOfDecl(Sema &S, NamedDecl *D, SourceLocation Loc,
if (AvailabilityResult Result =
S.ShouldDiagnoseAvailabilityOfDecl(D, ContextVersion, &Message)) {

if (Result == AR_NotYetIntroduced && S.getCurFunctionOrMethodDecl()) {
S.getEnclosingFunction()->HasPotentialAvailabilityViolations = true;
return;
}

const ObjCPropertyDecl *ObjCPDecl = nullptr;
if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
Expand Down Expand Up @@ -15198,11 +15203,6 @@ ExprResult Sema::ActOnObjCAvailabilityCheckExpr(
VersionTuple Version;
if (Spec != AvailSpecs.end())
Version = Spec->getVersion();
else
// This is the '*' case in @available. We should diagnose this; the
// programmer should explicitly account for this case if they target this
// platform.
Diag(AtLoc, diag::warn_available_using_star_case) << RParen << Platform;

return new (Context)
ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr,
if (Cond.isInvalid())
return StmtError();

if (IsConstexpr)
if (IsConstexpr || isa<ObjCAvailabilityCheckExpr>(Cond.get().second))
getCurFunction()->setHasBranchProtectedScope();

DiagnoseUnusedExprResult(thenStmt);
Expand Down
2 changes: 0 additions & 2 deletions clang/test/Parser/objc-available.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ void f() {

(void)@available(erik_os 10.10, hat_os 1.0, *); // expected-error 2 {{unrecognized platform name}}

(void)@available(ios 8, *); // expected-warning{{using '*' case here, platform macos is not accounted for}}

(void)@available(); // expected-error{{expected a platform name here}}
(void)@available(macos 10.10,); // expected-error{{expected a platform name here}}
(void)@available(macos); // expected-error{{expected a version}}
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/attr-availability.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void test_10095131() {
ATSFontGetPostScriptName(100); // expected-error {{'ATSFontGetPostScriptName' is unavailable: obsoleted in macOS 9.0 - use ATSFontGetFullPostScriptName}}

#if defined(WARN_PARTIAL)
// expected-warning@+2 {{is partial: introduced in macOS 10.8}} expected-note@+2 {{explicitly redeclare 'PartiallyAvailable' to silence this warning}}
// expected-warning@+2 {{is only available on macOS 10.8 or newer}} expected-note@+2 {{enclose 'PartiallyAvailable' in an @available check to silence this warning}}
#endif
PartiallyAvailable();
}
Expand Down
Loading

0 comments on commit 5cd5717

Please sign in to comment.