Skip to content

Commit 5cd5717

Browse files
committed
[ObjC] Warn on unguarded use of partial declaration
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
1 parent c98ef71 commit 5cd5717

17 files changed

+395
-49
lines changed

clang/include/clang/AST/Stmt.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,8 @@ class IfStmt : public Stmt {
933933
bool isConstexpr() const { return IfStmtBits.IsConstexpr; }
934934
void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; }
935935

936+
bool isObjCAvailabilityCheck() const;
937+
936938
SourceLocation getLocStart() const LLVM_READONLY { return IfLoc; }
937939
SourceLocation getLocEnd() const LLVM_READONLY {
938940
if (SubExprs[ELSE])

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ def CXX11CompatDeprecatedWritableStr :
9595
def DeprecatedAttributes : DiagGroup<"deprecated-attributes">;
9696
def DeprecatedDeclarations : DiagGroup<"deprecated-declarations">;
9797
def UnavailableDeclarations : DiagGroup<"unavailable-declarations">;
98-
def PartialAvailability : DiagGroup<"partial-availability">;
9998
def UnguardedAvailability : DiagGroup<"unguarded-availability">;
99+
// partial-availability is an alias of unguarded-availability.
100+
def : DiagGroup<"partial-availability", [UnguardedAvailability]>;
100101
def DeprecatedImplementations :DiagGroup<"deprecated-implementations">;
101102
def DeprecatedIncrementBool : DiagGroup<"deprecated-increment-bool">;
102103
def DeprecatedRegister : DiagGroup<"deprecated-register">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,8 +2656,20 @@ def note_overridden_method : Note<
26562656
def note_protocol_method : Note<
26572657
"protocol method is here">;
26582658

2659-
def warn_available_using_star_case : Warning<
2660-
"using '*' case here, platform %0 is not accounted for">, InGroup<UnguardedAvailability>;
2659+
def warn_unguarded_availability :
2660+
Warning<"%0 is only available on %1 %2 or newer">,
2661+
InGroup<UnguardedAvailability>, DefaultIgnore;
2662+
def warn_partial_availability : Warning<"%0 is only available conditionally">,
2663+
InGroup<UnguardedAvailability>, DefaultIgnore;
2664+
def note_partial_availability_silence : Note<
2665+
"explicitly redeclare %0 to silence this warning">;
2666+
def note_unguarded_available_silence : Note<
2667+
"enclose %0 in an @available check to silence this warning">;
2668+
def warn_partial_message : Warning<"%0 is partial: %1">,
2669+
InGroup<UnguardedAvailability>, DefaultIgnore;
2670+
def warn_partial_fwdclass_message : Warning<
2671+
"%0 may be partial because the receiver type is unknown">,
2672+
InGroup<UnguardedAvailability>, DefaultIgnore;
26612673

26622674
// Thread Safety Attributes
26632675
def warn_invalid_capability_name : Warning<
@@ -4279,15 +4291,6 @@ def err_not_found_by_two_phase_lookup : Error<"call to function %0 that is neith
42794291
def note_not_found_by_two_phase_lookup : Note<"%0 should be declared prior to the "
42804292
"call site%select{| or in %2| or in an associated namespace of one of its arguments}1">;
42814293
def err_undeclared_use : Error<"use of undeclared %0">;
4282-
def warn_partial_availability : Warning<"%0 is only available conditionally">,
4283-
InGroup<PartialAvailability>, DefaultIgnore;
4284-
def note_partial_availability_silence : Note<
4285-
"explicitly redeclare %0 to silence this warning">;
4286-
def warn_partial_message : Warning<"%0 is partial: %1">,
4287-
InGroup<PartialAvailability>, DefaultIgnore;
4288-
def warn_partial_fwdclass_message : Warning<
4289-
"%0 may be partial because the receiver type is unknown">,
4290-
InGroup<PartialAvailability>, DefaultIgnore;
42914294
def warn_deprecated : Warning<"%0 is deprecated">,
42924295
InGroup<DeprecatedDeclarations>;
42934296
def warn_property_method_deprecated :
@@ -4738,6 +4741,8 @@ def note_protected_by_vla_type_alias : Note<
47384741
"jump bypasses initialization of VLA type alias">;
47394742
def note_protected_by_constexpr_if : Note<
47404743
"jump enters controlled statement of constexpr if">;
4744+
def note_protected_by_if_available : Note<
4745+
"jump enters controlled statement of if available">;
47414746
def note_protected_by_vla : Note<
47424747
"jump bypasses initialization of variable length array">;
47434748
def note_protected_by_objc_try : Note<

clang/include/clang/Sema/ScopeInfo.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,15 @@ class FunctionScopeInfo {
106106
bool HasDroppedStmt : 1;
107107

108108
/// \brief True if current scope is for OpenMP declare reduction combiner.
109-
bool HasOMPDeclareReductionCombiner;
109+
bool HasOMPDeclareReductionCombiner : 1;
110110

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

114+
/// \brief Whether we make reference to a declaration that could be
115+
/// unavailable.
116+
bool HasPotentialAvailabilityViolations : 1;
117+
114118
/// A flag that is set when parsing a method that must call super's
115119
/// implementation, such as \c -dealloc, \c -finalize, or any method marked
116120
/// with \c __attribute__((objc_requires_super)).
@@ -381,6 +385,7 @@ class FunctionScopeInfo {
381385
HasDroppedStmt(false),
382386
HasOMPDeclareReductionCombiner(false),
383387
HasFallthroughStmt(false),
388+
HasPotentialAvailabilityViolations(false),
384389
ObjCShouldCallSuper(false),
385390
ObjCIsDesignatedInit(false),
386391
ObjCWarnForNoDesignatedInitChain(false),

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,6 +3639,9 @@ class Sema {
36393639
bool makeUnavailableInSystemHeader(SourceLocation loc,
36403640
UnavailableAttr::ImplicitReason reason);
36413641

3642+
/// \brief Issue any -Wunguarded-availability warnings in \c FD
3643+
void DiagnoseUnguardedAvailabilityViolations(Decl *FD);
3644+
36423645
//===--------------------------------------------------------------------===//
36433646
// Expression Parsing Callbacks: SemaExpr.cpp.
36443647

clang/lib/AST/Stmt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,10 @@ void IfStmt::setConditionVariable(const ASTContext &C, VarDecl *V) {
794794
VarRange.getEnd());
795795
}
796796

797+
bool IfStmt::isObjCAvailabilityCheck() const {
798+
return isa<ObjCAvailabilityCheckExpr>(SubExprs[COND]);
799+
}
800+
797801
ForStmt::ForStmt(const ASTContext &C, Stmt *Init, Expr *Cond, VarDecl *condVar,
798802
Expr *Inc, Stmt *Body, SourceLocation FL, SourceLocation LP,
799803
SourceLocation RP)

clang/lib/Sema/JumpDiagnostics.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,30 +325,27 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
325325

326326
case Stmt::IfStmtClass: {
327327
IfStmt *IS = cast<IfStmt>(S);
328-
if (!IS->isConstexpr())
328+
if (!(IS->isConstexpr() || IS->isObjCAvailabilityCheck()))
329329
break;
330330

331+
unsigned Diag = IS->isConstexpr() ? diag::note_protected_by_constexpr_if
332+
: diag::note_protected_by_if_available;
333+
331334
if (VarDecl *Var = IS->getConditionVariable())
332335
BuildScopeInformation(Var, ParentScope);
333336

334337
// Cannot jump into the middle of the condition.
335338
unsigned NewParentScope = Scopes.size();
336-
Scopes.push_back(GotoScope(ParentScope,
337-
diag::note_protected_by_constexpr_if, 0,
338-
IS->getLocStart()));
339+
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
339340
BuildScopeInformation(IS->getCond(), NewParentScope);
340341

341342
// Jumps into either arm of an 'if constexpr' are not allowed.
342343
NewParentScope = Scopes.size();
343-
Scopes.push_back(GotoScope(ParentScope,
344-
diag::note_protected_by_constexpr_if, 0,
345-
IS->getLocStart()));
344+
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
346345
BuildScopeInformation(IS->getThen(), NewParentScope);
347346
if (Stmt *Else = IS->getElse()) {
348347
NewParentScope = Scopes.size();
349-
Scopes.push_back(GotoScope(ParentScope,
350-
diag::note_protected_by_constexpr_if, 0,
351-
IS->getLocStart()));
348+
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getLocStart()));
352349
BuildScopeInformation(Else, NewParentScope);
353350
}
354351
return;

clang/lib/Sema/ScopeInfo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void FunctionScopeInfo::Clear() {
2929
HasIndirectGoto = false;
3030
HasDroppedStmt = false;
3131
HasOMPDeclareReductionCombiner = false;
32+
HasPotentialAvailabilityViolations = false;
3233
ObjCShouldCallSuper = false;
3334
ObjCIsDesignatedInit = false;
3435
ObjCWarnForNoDesignatedInitChain = false;

clang/lib/Sema/SemaDecl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11736,6 +11736,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
1173611736
return nullptr;
1173711737
}
1173811738

11739+
if (Body && getCurFunction()->HasPotentialAvailabilityViolations)
11740+
DiagnoseUnguardedAvailabilityViolations(dcl);
11741+
1173911742
assert(!getCurFunction()->ObjCShouldCallSuper &&
1174011743
"This should only be set for ObjC methods, which should have been "
1174111744
"handled in the block above.");

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/AST/Expr.h"
2222
#include "clang/AST/ExprCXX.h"
2323
#include "clang/AST/Mangle.h"
24+
#include "clang/AST/RecursiveASTVisitor.h"
2425
#include "clang/Basic/CharInfo.h"
2526
#include "clang/Basic/SourceManager.h"
2627
#include "clang/Basic/TargetInfo.h"
@@ -6330,6 +6331,9 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
63306331
break;
63316332

63326333
case AR_NotYetIntroduced:
6334+
assert(!S.getCurFunctionOrMethodDecl() &&
6335+
"Function-level partial availablity should not be diagnosed here!");
6336+
63336337
diag = diag::warn_partial_availability;
63346338
diag_message = diag::warn_partial_message;
63356339
diag_fwdclass_message = diag::warn_partial_fwdclass_message;
@@ -6511,3 +6515,146 @@ VersionTuple Sema::getVersionForDecl(const Decl *D) const {
65116515

65126516
return std::max(DeclVersion, Context.getTargetInfo().getPlatformMinVersion());
65136517
}
6518+
6519+
namespace {
6520+
6521+
/// \brief This class implements -Wunguarded-availability.
6522+
///
6523+
/// This is done with a traversal of the AST of a function that makes reference
6524+
/// to a partially available declaration. Whenever we encounter an \c if of the
6525+
/// form: \c if(@available(...)), we use the version from the condition to visit
6526+
/// the then statement.
6527+
class DiagnoseUnguardedAvailability
6528+
: public RecursiveASTVisitor<DiagnoseUnguardedAvailability> {
6529+
typedef RecursiveASTVisitor<DiagnoseUnguardedAvailability> Base;
6530+
6531+
Sema &SemaRef;
6532+
6533+
/// Stack of potentially nested 'if (@available(...))'s.
6534+
SmallVector<VersionTuple, 8> AvailabilityStack;
6535+
6536+
void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range);
6537+
6538+
public:
6539+
DiagnoseUnguardedAvailability(Sema &SemaRef, VersionTuple BaseVersion)
6540+
: SemaRef(SemaRef) {
6541+
AvailabilityStack.push_back(BaseVersion);
6542+
}
6543+
6544+
void IssueDiagnostics(Stmt *S) { TraverseStmt(S); }
6545+
6546+
bool TraverseIfStmt(IfStmt *If);
6547+
6548+
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
6549+
if (ObjCMethodDecl *D = Msg->getMethodDecl())
6550+
DiagnoseDeclAvailability(
6551+
D, SourceRange(Msg->getSelectorStartLoc(), Msg->getLocEnd()));
6552+
return true;
6553+
}
6554+
6555+
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
6556+
DiagnoseDeclAvailability(DRE->getDecl(),
6557+
SourceRange(DRE->getLocStart(), DRE->getLocEnd()));
6558+
return true;
6559+
}
6560+
6561+
bool VisitMemberExpr(MemberExpr *ME) {
6562+
DiagnoseDeclAvailability(ME->getMemberDecl(),
6563+
SourceRange(ME->getLocStart(), ME->getLocEnd()));
6564+
return true;
6565+
}
6566+
6567+
bool VisitTypeLoc(TypeLoc Ty);
6568+
};
6569+
6570+
void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
6571+
NamedDecl *D, SourceRange Range) {
6572+
6573+
VersionTuple ContextVersion = AvailabilityStack.back();
6574+
if (AvailabilityResult Result = SemaRef.ShouldDiagnoseAvailabilityOfDecl(
6575+
D, ContextVersion, nullptr)) {
6576+
// All other diagnostic kinds have already been handled in
6577+
// DiagnoseAvailabilityOfDecl.
6578+
if (Result != AR_NotYetIntroduced)
6579+
return;
6580+
6581+
const AvailabilityAttr *AA = getAttrForPlatform(SemaRef.getASTContext(), D);
6582+
VersionTuple Introduced = AA->getIntroduced();
6583+
6584+
SemaRef.Diag(Range.getBegin(), diag::warn_unguarded_availability)
6585+
<< Range << D
6586+
<< AvailabilityAttr::getPrettyPlatformName(
6587+
SemaRef.getASTContext().getTargetInfo().getPlatformName())
6588+
<< Introduced.getAsString();
6589+
6590+
SemaRef.Diag(D->getLocation(), diag::note_availability_specified_here)
6591+
<< D << /* partial */ 3;
6592+
6593+
// FIXME: Replace this with a fixit diagnostic.
6594+
SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
6595+
<< Range << D;
6596+
}
6597+
}
6598+
6599+
bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
6600+
const Type *TyPtr = Ty.getTypePtr();
6601+
SourceRange Range{Ty.getBeginLoc(), Ty.getEndLoc()};
6602+
6603+
if (const TagType *TT = dyn_cast<TagType>(TyPtr)) {
6604+
TagDecl *TD = TT->getDecl();
6605+
DiagnoseDeclAvailability(TD, Range);
6606+
6607+
} else if (const TypedefType *TD = dyn_cast<TypedefType>(TyPtr)) {
6608+
TypedefNameDecl *D = TD->getDecl();
6609+
DiagnoseDeclAvailability(D, Range);
6610+
6611+
} else if (const auto *ObjCO = dyn_cast<ObjCObjectType>(TyPtr)) {
6612+
if (NamedDecl *D = ObjCO->getInterface())
6613+
DiagnoseDeclAvailability(D, Range);
6614+
}
6615+
6616+
return true;
6617+
}
6618+
6619+
bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
6620+
VersionTuple CondVersion;
6621+
if (auto *E = dyn_cast<ObjCAvailabilityCheckExpr>(If->getCond())) {
6622+
CondVersion = E->getVersion();
6623+
6624+
// If we're using the '*' case here or if this check is redundant, then we
6625+
// use the enclosing version to check both branches.
6626+
if (CondVersion.empty() || CondVersion <= AvailabilityStack.back())
6627+
return Base::TraverseStmt(If->getThen()) &&
6628+
Base::TraverseStmt(If->getElse());
6629+
} else {
6630+
// This isn't an availability checking 'if', we can just continue.
6631+
return Base::TraverseIfStmt(If);
6632+
}
6633+
6634+
AvailabilityStack.push_back(CondVersion);
6635+
bool ShouldContinue = TraverseStmt(If->getThen());
6636+
AvailabilityStack.pop_back();
6637+
6638+
return ShouldContinue && TraverseStmt(If->getElse());
6639+
}
6640+
6641+
} // end anonymous namespace
6642+
6643+
void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) {
6644+
Stmt *Body = nullptr;
6645+
6646+
if (auto *FD = D->getAsFunction()) {
6647+
// FIXME: We only examine the pattern decl for availability violations now,
6648+
// but we should also examine instantiated templates.
6649+
if (FD->isTemplateInstantiation())
6650+
return;
6651+
6652+
Body = FD->getBody();
6653+
} else if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
6654+
Body = MD->getBody();
6655+
6656+
assert(Body && "Need a body here!");
6657+
6658+
VersionTuple BaseVersion = getVersionForDecl(D);
6659+
DiagnoseUnguardedAvailability(*this, BaseVersion).IssueDiagnostics(Body);
6660+
}

clang/lib/Sema/SemaExpr.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ DiagnoseAvailabilityOfDecl(Sema &S, NamedDecl *D, SourceLocation Loc,
184184
if (AvailabilityResult Result =
185185
S.ShouldDiagnoseAvailabilityOfDecl(D, ContextVersion, &Message)) {
186186

187+
if (Result == AR_NotYetIntroduced && S.getCurFunctionOrMethodDecl()) {
188+
S.getEnclosingFunction()->HasPotentialAvailabilityViolations = true;
189+
return;
190+
}
191+
187192
const ObjCPropertyDecl *ObjCPDecl = nullptr;
188193
if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
189194
if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
@@ -15198,11 +15203,6 @@ ExprResult Sema::ActOnObjCAvailabilityCheckExpr(
1519815203
VersionTuple Version;
1519915204
if (Spec != AvailSpecs.end())
1520015205
Version = Spec->getVersion();
15201-
else
15202-
// This is the '*' case in @available. We should diagnose this; the
15203-
// programmer should explicitly account for this case if they target this
15204-
// platform.
15205-
Diag(AtLoc, diag::warn_available_using_star_case) << RParen << Platform;
1520615206

1520715207
return new (Context)
1520815208
ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);

clang/lib/Sema/SemaStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr,
536536
if (Cond.isInvalid())
537537
return StmtError();
538538

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

542542
DiagnoseUnusedExprResult(thenStmt);

clang/test/Parser/objc-available.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ void f() {
1515

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

18-
(void)@available(ios 8, *); // expected-warning{{using '*' case here, platform macos is not accounted for}}
19-
2018
(void)@available(); // expected-error{{expected a platform name here}}
2119
(void)@available(macos 10.10,); // expected-error{{expected a platform name here}}
2220
(void)@available(macos); // expected-error{{expected a version}}

clang/test/Sema/attr-availability.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ void test_10095131() {
3030
ATSFontGetPostScriptName(100); // expected-error {{'ATSFontGetPostScriptName' is unavailable: obsoleted in macOS 9.0 - use ATSFontGetFullPostScriptName}}
3131

3232
#if defined(WARN_PARTIAL)
33-
// expected-warning@+2 {{is partial: introduced in macOS 10.8}} expected-note@+2 {{explicitly redeclare 'PartiallyAvailable' to silence this warning}}
33+
// 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}}
3434
#endif
3535
PartiallyAvailable();
3636
}

0 commit comments

Comments
 (0)