Skip to content

Commit 59b92c6

Browse files
authored
Diagnose deprecated availability domains (#11492)
rdar://157603372
1 parent db0d747 commit 59b92c6

File tree

10 files changed

+363
-1
lines changed

10 files changed

+363
-1
lines changed

clang/include/clang/AST/ASTContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
919919
FeatureAvailKind Kind = FeatureAvailKind::None;
920920
clang::Decl *Decl = nullptr;
921921
ImplicitCastExpr *Call = nullptr;
922+
bool IsDeprecated = false;
922923
bool isInvalid() const { return Kind == FeatureAvailKind::None; }
923924
};
924925

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion,
287287
]>,
288288
DiagCategory<"Deprecations">;
289289

290+
def PermanentlyAvailableDomain : DiagGroup<"permanently-available-domain">;
291+
290292
def CXX20Designator : DiagGroup<"c++20-designator">;
291293
// Allow -Wno-c99-designator to be used to turn off all warnings on valid C99
292294
// designators (including the warning controlled by -Wc++20-designator).

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4136,6 +4136,14 @@ def err_features_invalid_for_decl : Error<
41364136
"feature attributes cannot be applied to %0">;
41374137
def err_features_invalid_name : Error<
41384138
"cannot find definition of feature attribute '%0'">;
4139+
def warn_deprecated_availability_domain : Warning<
4140+
"availability domain '%0' is deprecated">, InGroup<DiagGroup<"deprecated-availability-domain">>;
4141+
def warn_permanently_available_domain_expr : Warning<
4142+
"unnecessary check for '%0'; this expression will always evaluate to true">,
4143+
InGroup<PermanentlyAvailableDomain>;
4144+
def warn_permanently_available_domain_decl : Warning<
4145+
"%select{attribute has no effect because '%0' is always available|declaration is permanently unavailable because '%0' is always available}1">,
4146+
InGroup<PermanentlyAvailableDomain>;
41394147
def err_features_invalid__arg : Error<
41404148
"invalid argument %0: must evaluate to 0 or 1">;
41414149
def err_feature_invalid_for_decl : Error<

clang/include/clang/Sema/Sema.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class InitializedEntity;
143143
enum class LangAS : unsigned int;
144144
class LocalInstantiationScope;
145145
class LookupResult;
146+
class MacroInfo;
146147
class MangleNumberingContext;
147148
typedef ArrayRef<IdentifierLoc> ModuleIdPath;
148149
class ModuleLoader;
@@ -2670,6 +2671,15 @@ class Sema final : public SemaBase {
26702671
void DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D,
26712672
ArrayRef<SourceLocation> Locs);
26722673

2674+
void diagnoseDeprecatedAvailabilityDomain(StringRef DomainName,
2675+
SourceLocation AvailLoc,
2676+
SourceLocation DomainLoc,
2677+
bool IsUnavailable = false,
2678+
const ParsedAttr *PA = nullptr);
2679+
2680+
/// A map that indicates whether a macro is simple.
2681+
llvm::SmallDenseMap<clang::MacroInfo *, bool> SimpleFeatureAvailabiltyMacros;
2682+
26732683
/// Retrieve the current function, if any, that should be analyzed for
26742684
/// potential availability violations.
26752685
sema::FunctionScopeInfo *getCurFunctionAvailabilityContext();

clang/lib/AST/ASTContext.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,8 @@ ASTContext::getFeatureAvailInfo(Decl *D) const {
997997
llvm_unreachable("invalid feature kind");
998998
}
999999

1000-
ASTContext::AvailabilityDomainInfo Info{Kind, D, nullptr};
1000+
ASTContext::AvailabilityDomainInfo Info{Kind, D, nullptr,
1001+
D->hasAttr<DeprecatedAttr>()};
10011002

10021003
if (Kind == FeatureAvailKind::Dynamic) {
10031004
Expr *FnExpr = Init->getInit(1);

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,6 +2492,10 @@ static void handleFeatureAvailabilityAttr(Sema &S, Decl *D,
24922492
int IsUnavailable =
24932493
AL.getArgAsExpr(1)->getIntegerConstantExpr(S.Context)->getExtValue();
24942494

2495+
S.diagnoseDeprecatedAvailabilityDomain(II->getName(), AL.getLoc(),
2496+
AL.getArgAsIdent(0)->getLoc(),
2497+
IsUnavailable, &AL);
2498+
24952499
if (IsUnavailable != 0 && IsUnavailable != 1) {
24962500
S.Diag(AL.getArgAsExpr(1)->getExprLoc(), diag::err_features_invalid__arg)
24972501
<< IsUnavailable;

clang/lib/Sema/SemaExprObjC.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5162,6 +5162,10 @@ ExprResult SemaObjC::ActOnObjCAvailabilityCheckExpr(
51625162
if (FunctionScopeInfo *Info = SemaRef.getCurFunctionAvailabilityContext())
51635163
Info->HasPotentialFeatureAvailabilityViolations = true;
51645164
auto Spec = AvailSpecs.front();
5165+
5166+
SemaRef.diagnoseDeprecatedAvailabilityDomain(Spec.getDomainName(), AtLoc,
5167+
Spec.getBeginLoc());
5168+
51655169
return ObjCAvailabilityCheckExpr::CreateAvailabilityFeatureCheck(
51665170
AtLoc, RParen, Context.BoolTy, Spec.getDomainName(), Context);
51675171
}

clang/lib/Sema/SemaFeatureAvailability.cpp

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include "clang/AST/Attr.h"
1414
#include "clang/AST/Decl.h"
1515
#include "clang/AST/RecursiveASTVisitor.h"
16+
#include "clang/Basic/SourceManager.h"
17+
#include "clang/Lex/Lexer.h"
18+
#include "clang/Lex/Preprocessor.h"
1619
#include "clang/Sema/DelayedDiagnostic.h"
1720
#include "clang/Sema/ScopeInfo.h"
1821
#include "clang/Sema/Sema.h"
@@ -240,3 +243,156 @@ void Sema::DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D,
240243
Decl *Ctx = cast<Decl>(getCurLexicalContext());
241244
diagnoseDeclFeatureAvailability(D, Locs.front(), Ctx, *this);
242245
}
246+
247+
static bool isSimpleFeatureAvailabiltyMacro(MacroInfo *Info) {
248+
// Must match:
249+
// __attribute__((availability(domain : id, id/numeric_constant)))
250+
if (Info->getNumTokens() != 13)
251+
return false;
252+
253+
if (!Info->getReplacementToken(0).is(tok::kw___attribute) ||
254+
!Info->getReplacementToken(1).is(tok::l_paren) ||
255+
!Info->getReplacementToken(2).is(tok::l_paren))
256+
return false;
257+
258+
if (const Token &Tk = Info->getReplacementToken(3);
259+
!Tk.is(tok::identifier) ||
260+
Tk.getIdentifierInfo()->getName() != "availability")
261+
return false;
262+
263+
if (!Info->getReplacementToken(4).is(tok::l_paren))
264+
return false;
265+
266+
if (const Token &Tk = Info->getReplacementToken(5);
267+
!Tk.is(tok::identifier) || Tk.getIdentifierInfo()->getName() != "domain")
268+
return false;
269+
270+
if (!Info->getReplacementToken(6).is(tok::colon))
271+
return false;
272+
273+
if (const Token &Tk = Info->getReplacementToken(7); !Tk.is(tok::identifier))
274+
return false;
275+
276+
if (!Info->getReplacementToken(8).is(tok::comma))
277+
return false;
278+
279+
if (const Token &Tk = Info->getReplacementToken(9);
280+
!Tk.is(tok::identifier) && !Tk.is(tok::numeric_constant))
281+
return false;
282+
283+
if (!Info->getReplacementToken(10).is(tok::r_paren) ||
284+
!Info->getReplacementToken(11).is(tok::r_paren) ||
285+
!Info->getReplacementToken(12).is(tok::r_paren))
286+
return false;
287+
288+
return true;
289+
}
290+
291+
void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName,
292+
SourceLocation AvailLoc,
293+
SourceLocation DomainLoc,
294+
bool IsUnavailable,
295+
const ParsedAttr *PA) {
296+
auto CreateFixIt = [&]() {
297+
if (PA->getRange().getBegin().isMacroID()) {
298+
auto *MacroII = PA->getMacroIdentifier();
299+
300+
// Macro identifier isn't always set.
301+
if (!MacroII)
302+
return FixItHint{};
303+
304+
MacroDefinition MD = PP.getMacroDefinition(MacroII);
305+
MacroInfo *Info = MD.getMacroInfo();
306+
307+
bool IsSimple;
308+
auto It = SimpleFeatureAvailabiltyMacros.find(Info);
309+
310+
if (It == SimpleFeatureAvailabiltyMacros.end()) {
311+
IsSimple = isSimpleFeatureAvailabiltyMacro(Info);
312+
SimpleFeatureAvailabiltyMacros[Info] = IsSimple;
313+
} else {
314+
IsSimple = It->second;
315+
}
316+
317+
if (!IsSimple)
318+
return FixItHint{};
319+
320+
FileID FID = SourceMgr.getFileID(AvailLoc);
321+
const SrcMgr::ExpansionInfo *EI =
322+
&SourceMgr.getSLocEntry(FID).getExpansion();
323+
if (IsUnavailable)
324+
return FixItHint::CreateReplacement(EI->getExpansionLocRange(),
325+
"__attribute__((unavailable))");
326+
return FixItHint::CreateRemoval(EI->getExpansionLocRange());
327+
}
328+
329+
if (PA->getSyntax() != AttributeCommonInfo::Syntax::AS_GNU)
330+
return FixItHint{};
331+
332+
SourceRange AttrRange = PA->getRange();
333+
334+
// Replace the availability attribute with "unavailable".
335+
if (IsUnavailable)
336+
return FixItHint::CreateReplacement(AttrRange, "unavailable");
337+
338+
// Remove the availability attribute.
339+
340+
// If there is a leading comma, there's another operand that precedes the
341+
// availability attribute. In that case, remove the availability attribute
342+
// and the comma.
343+
Token PrevTok = *Lexer::findPreviousToken(AttrRange.getBegin(), SourceMgr,
344+
getLangOpts(), false);
345+
if (PrevTok.is(tok::comma))
346+
return FixItHint::CreateRemoval(
347+
SourceRange(PrevTok.getLocation(), AttrRange.getEnd()));
348+
349+
// If there is a trailing comma, there's another operand that follows the
350+
// availability attribute. In that case, remove the availability attribute
351+
// and the comma.
352+
Token NextTok = *Lexer::findNextToken(AttrRange.getEnd(), SourceMgr,
353+
getLangOpts(), false);
354+
if (NextTok.is(tok::comma))
355+
return FixItHint::CreateRemoval(
356+
SourceRange(AttrRange.getBegin(), NextTok.getLocation()));
357+
358+
// If no leading or trailing commas are found, the availability attribute is
359+
// the only operand. Remove the entire attribute construct.
360+
361+
// Look for '__attribute'.
362+
for (int i = 0; i < 2; ++i)
363+
PrevTok = *Lexer::findPreviousToken(PrevTok.getLocation(), SourceMgr,
364+
getLangOpts(), false);
365+
if (!PrevTok.is(tok::raw_identifier) ||
366+
PrevTok.getRawIdentifier() != "__attribute__")
367+
return FixItHint{};
368+
369+
// Look for the closing ')'.
370+
NextTok = *Lexer::findNextToken(NextTok.getLocation(), SourceMgr,
371+
getLangOpts(), false);
372+
if (!NextTok.is(tok::r_paren))
373+
return FixItHint{};
374+
375+
return FixItHint::CreateRemoval(
376+
SourceRange(PrevTok.getLocation(), NextTok.getLocation()));
377+
};
378+
379+
ASTContext::AvailabilityDomainInfo Info =
380+
Context.getFeatureAvailInfo(DomainName);
381+
382+
if (Info.IsDeprecated) {
383+
Diag(DomainLoc, diag::warn_deprecated_availability_domain) << DomainName;
384+
if (Info.Kind == FeatureAvailKind::AlwaysAvailable) {
385+
if (PA) {
386+
auto FixitDiag =
387+
Diag(AvailLoc, diag::warn_permanently_available_domain_decl)
388+
<< DomainName << IsUnavailable;
389+
390+
FixItHint Hint = CreateFixIt();
391+
if (!Hint.isNull())
392+
FixitDiag << Hint;
393+
} else
394+
Diag(AvailLoc, diag::warn_permanently_available_domain_expr)
395+
<< DomainName;
396+
}
397+
}
398+
}

0 commit comments

Comments
 (0)