Skip to content

Commit 45ef823

Browse files
committed
[domain availability] Offer fix-it hints for statements that need to be
wrapped in if (@available(domain: ...)) checks rdar://161436694
1 parent 7657b41 commit 45ef823

File tree

4 files changed

+286
-9
lines changed

4 files changed

+286
-9
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4131,6 +4131,10 @@ def note_feature_incompatible0 : Note<
41314131
"feature attribute %0">;
41324132
def note_feature_incompatible1 : Note<
41334133
"is incompatible with %0">;
4134+
def note_silence_unguarded_availability_domain : Note<
4135+
"enclose %0 in %select{an @available|a __builtin_available}1 check to silence "
4136+
"this warning">;
4137+
41344138
def warn_unguarded_availability_unavailable :
41354139
Warning<"%0 is unavailable">,
41364140
InGroup<UnguardedAvailability>, DefaultIgnore;

clang/lib/Sema/SemaFeatureAvailability.cpp

Lines changed: 146 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class DiagnoseUnguardedFeatureAvailability
6363
bool Unavailable;
6464
};
6565

66+
SmallVector<const Stmt *, 16> StmtStack;
6667
SmallVector<FeatureAvailInfo, 4> FeatureStack;
6768

6869
bool isFeatureUseGuarded(const DomainAvailabilityAttr *Attr) const;
@@ -74,7 +75,16 @@ class DiagnoseUnguardedFeatureAvailability
7475
Decl *Ctx = nullptr)
7576
: SemaRef(SemaRef), D(D) {}
7677

77-
void diagnoseDeclFeatureAvailability(const NamedDecl *D, SourceLocation Loc);
78+
void diagnoseDeclFeatureAvailability(const NamedDecl *D, SourceRange Range);
79+
80+
bool TraverseStmt(Stmt *S) {
81+
if (!S)
82+
return true;
83+
StmtStack.push_back(S);
84+
bool Result = Base::TraverseStmt(S);
85+
StmtStack.pop_back();
86+
return Result;
87+
}
7888

7989
bool TraverseIfStmt(IfStmt *If);
8090

@@ -84,18 +94,18 @@ class DiagnoseUnguardedFeatureAvailability
8494
}
8595

8696
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
87-
diagnoseDeclFeatureAvailability(DRE->getDecl(), DRE->getBeginLoc());
97+
diagnoseDeclFeatureAvailability(DRE->getDecl(), DRE->getSourceRange());
8898
return true;
8999
}
90100

91101
bool VisitMemberExpr(MemberExpr *ME) {
92-
diagnoseDeclFeatureAvailability(ME->getMemberDecl(), ME->getBeginLoc());
102+
diagnoseDeclFeatureAvailability(ME->getMemberDecl(), ME->getSourceRange());
93103
return true;
94104
}
95105

96106
bool VisitObjCMessageExpr(ObjCMessageExpr *OME) {
97107
if (auto *MD = OME->getMethodDecl())
98-
diagnoseDeclFeatureAvailability(MD, OME->getBeginLoc());
108+
diagnoseDeclFeatureAvailability(MD, OME->getSourceRange());
99109
return true;
100110
}
101111

@@ -172,8 +182,61 @@ bool DiagnoseUnguardedFeatureAvailability::isFeatureUseGuarded(
172182
return ::isFeatureUseGuarded(Attr, D, SemaRef.Context);
173183
}
174184

185+
/// Returns true if the given statement can be a body-like child of \p Parent.
186+
bool isBodyLikeChildStmt(const Stmt *S, const Stmt *Parent) {
187+
switch (Parent->getStmtClass()) {
188+
case Stmt::IfStmtClass:
189+
return cast<IfStmt>(Parent)->getThen() == S ||
190+
cast<IfStmt>(Parent)->getElse() == S;
191+
case Stmt::WhileStmtClass:
192+
return cast<WhileStmt>(Parent)->getBody() == S;
193+
case Stmt::DoStmtClass:
194+
return cast<DoStmt>(Parent)->getBody() == S;
195+
case Stmt::ForStmtClass:
196+
return cast<ForStmt>(Parent)->getBody() == S;
197+
case Stmt::CXXForRangeStmtClass:
198+
return cast<CXXForRangeStmt>(Parent)->getBody() == S;
199+
case Stmt::ObjCForCollectionStmtClass:
200+
return cast<ObjCForCollectionStmt>(Parent)->getBody() == S;
201+
case Stmt::CaseStmtClass:
202+
case Stmt::DefaultStmtClass:
203+
return cast<SwitchCase>(Parent)->getSubStmt() == S;
204+
default:
205+
return false;
206+
}
207+
}
208+
209+
/// Traverses the AST and finds the last statement that used a declaration in
210+
/// the given list.
211+
class LastDeclUSEFinder : public RecursiveASTVisitor<LastDeclUSEFinder> {
212+
llvm::SmallSet<const Decl *, 4> Decls;
213+
214+
public:
215+
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
216+
if (Decls.contains(DRE->getDecl()))
217+
return false;
218+
return true;
219+
}
220+
221+
static const Stmt *findLastStmtThatUsesDecl(const DeclStmt *UseStmt,
222+
const CompoundStmt *Scope) {
223+
LastDeclUSEFinder Visitor;
224+
Visitor.Decls.insert(UseStmt->decl_begin(), UseStmt->decl_end());
225+
const Stmt *LastUse = UseStmt;
226+
for (const Stmt *S : Scope->body()) {
227+
if (!Visitor.TraverseStmt(const_cast<Stmt *>(S)))
228+
LastUse = S;
229+
if (auto *DS = dyn_cast<DeclStmt>(S))
230+
Visitor.Decls.insert(DS->decl_begin(), DS->decl_end());
231+
}
232+
return LastUse;
233+
}
234+
};
235+
175236
void DiagnoseUnguardedFeatureAvailability::diagnoseDeclFeatureAvailability(
176-
const NamedDecl *D, SourceLocation Loc) {
237+
const NamedDecl *D, SourceRange Range) {
238+
SourceLocation Loc = Range.getBegin();
239+
SmallVector<const DomainAvailabilityAttr *, 1> Attrs;
177240
for (auto *Attr : D->specific_attrs<DomainAvailabilityAttr>()) {
178241
std::string FeatureUse = Attr->getDomain().str();
179242
// Skip checking if the feature is always enabled.
@@ -182,10 +245,84 @@ void DiagnoseUnguardedFeatureAvailability::diagnoseDeclFeatureAvailability(
182245
FeatureAvailKind::AlwaysAvailable)
183246
continue;
184247

185-
if (!isFeatureUseGuarded(Attr))
248+
if (!isFeatureUseGuarded(Attr)) {
186249
SemaRef.Diag(Loc, diag::err_unguarded_feature)
187250
<< D << FeatureUse << Attr->getUnavailable();
251+
Attrs.push_back(Attr);
252+
}
253+
}
254+
255+
if (Attrs.empty())
256+
return;
257+
258+
auto FixitDiag =
259+
SemaRef.Diag(Range.getBegin(),
260+
diag::note_silence_unguarded_availability_domain)
261+
<< Range << D
262+
<< (SemaRef.getLangOpts().ObjC ? /*@available*/ 0
263+
: /*__builtin_available*/ 1);
264+
265+
// Find the statement which should be enclosed in the if @available check.
266+
if (StmtStack.empty())
267+
return;
268+
269+
const Stmt *StmtOfUse = StmtStack.back();
270+
const CompoundStmt *Scope = nullptr;
271+
for (const Stmt *S : llvm::reverse(StmtStack)) {
272+
if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
273+
Scope = CS;
274+
break;
275+
}
276+
if (isBodyLikeChildStmt(StmtOfUse, S)) {
277+
// The declaration won't be seen outside of the statement, so we don't
278+
// have to wrap the uses of any declared variables in if (@available).
279+
// Therefore we can avoid setting Scope here.
280+
break;
281+
}
282+
283+
StmtOfUse = S;
188284
}
285+
286+
const Stmt *LastStmtOfUse = nullptr;
287+
if (auto *DS = dyn_cast<DeclStmt>(StmtOfUse); DS && Scope)
288+
LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(DS, Scope);
289+
290+
const SourceManager &SM = SemaRef.getSourceManager();
291+
SourceLocation IfInsertionLoc = SM.getExpansionLoc(StmtOfUse->getBeginLoc());
292+
SourceLocation StmtEndLoc =
293+
SM.getExpansionRange(
294+
(LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc())
295+
.getEnd();
296+
297+
if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc))
298+
return;
299+
300+
std::string Prefix;
301+
llvm::raw_string_ostream FixItOS(Prefix);
302+
StringRef AvailStr =
303+
SemaRef.getLangOpts().ObjC ? "@available" : "__builtin_available";
304+
for (auto *A : Attrs) {
305+
FixItOS << "if (" << AvailStr << "(domain:" << A->getDomain() << ")) {";
306+
if (A->getUnavailable())
307+
FixItOS << "} else {";
308+
}
309+
310+
FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str());
311+
FixItOS.str().clear();
312+
313+
for (auto *A : llvm::reverse(Attrs)) {
314+
FixItOS << "}";
315+
if (!A->getUnavailable())
316+
FixItOS << " else {}";
317+
}
318+
319+
SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken(
320+
StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(),
321+
/*SkipTrailingWhitespaceAndNewLine=*/false);
322+
if (ElseInsertionLoc.isInvalid())
323+
ElseInsertionLoc =
324+
Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts());
325+
FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str());
189326
}
190327

191328
bool DiagnoseUnguardedFeatureAvailability::VisitTypeLoc(TypeLoc Ty) {
@@ -197,13 +334,13 @@ bool DiagnoseUnguardedFeatureAvailability::VisitTypeLoc(TypeLoc Ty) {
197334

198335
if (const auto *TT = dyn_cast<TagType>(TyPtr)) {
199336
TagDecl *TD = TT->getDecl();
200-
diagnoseDeclFeatureAvailability(TD, Ty.getBeginLoc());
337+
diagnoseDeclFeatureAvailability(TD, Ty.getSourceRange());
201338
} else if (const auto *TD = dyn_cast<TypedefType>(TyPtr)) {
202339
TypedefNameDecl *D = TD->getDecl();
203-
diagnoseDeclFeatureAvailability(D, Ty.getBeginLoc());
340+
diagnoseDeclFeatureAvailability(D, Ty.getSourceRange());
204341
} else if (const auto *ObjCO = dyn_cast<ObjCObjectType>(TyPtr)) {
205342
if (NamedDecl *D = ObjCO->getInterface())
206-
diagnoseDeclFeatureAvailability(D, Ty.getBeginLoc());
343+
diagnoseDeclFeatureAvailability(D, Ty.getSourceRange());
207344
}
208345

209346
return true;

0 commit comments

Comments
 (0)