@@ -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+
175236void 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
191328bool 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