Skip to content

Commit b83b232

Browse files
author
serge-sans-paille
committed
Introduce -Wreserved-identifier
Warn when a declaration uses an identifier that doesn't obey the reserved identifier rule from C and/or C++. Differential Revision: https://reviews.llvm.org/D93095
1 parent 46fa214 commit b83b232

18 files changed

+277
-22
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ Non-comprehensive list of changes in this release
6767
New Compiler Flags
6868
------------------
6969

70-
- ...
70+
- ``-Wreserved-identifier`` emits warning when user code uses reserved
71+
identifiers.
7172

7273
Deprecated Compiler Flags
7374
-------------------------

clang/include/clang/AST/Decl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,10 @@ class NamedDecl : public Decl {
356356
/// a C++ class.
357357
bool isCXXInstanceMember() const;
358358

359+
/// Determine if the declaration obeys the reserved identifier rules of the
360+
/// given language.
361+
ReservedIdentifierStatus isReserved(const LangOptions &LangOpts) const;
362+
359363
/// Determine what kind of linkage this entity has.
360364
///
361365
/// This is not the linkage as defined by the standard or the codegen notion

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,8 @@ def : DiagGroup<"sequence-point", [Unsequenced]>;
638638
// Preprocessor warnings.
639639
def AmbiguousMacro : DiagGroup<"ambiguous-macro">;
640640
def KeywordAsMacro : DiagGroup<"keyword-macro">;
641-
def ReservedIdAsMacro : DiagGroup<"reserved-id-macro">;
641+
def ReservedIdAsMacro : DiagGroup<"reserved-macro-identifier">;
642+
def ReservedIdAsMacroAlias : DiagGroup<"reserved-id-macro", [ReservedIdAsMacro]>;
642643

643644
// Just silence warnings about -Wstrict-aliasing for now.
644645
def : DiagGroup<"strict-aliasing=0">;
@@ -801,6 +802,9 @@ def LargeByValueCopy : DiagGroup<"large-by-value-copy">;
801802
def DuplicateArgDecl : DiagGroup<"duplicate-method-arg">;
802803
def SignedEnumBitfield : DiagGroup<"signed-enum-bitfield">;
803804

805+
def ReservedIdentifier : DiagGroup<"reserved-identifier",
806+
[ReservedIdAsMacro]>;
807+
804808
// Unreachable code warning groups.
805809
//
806810
// The goal is make -Wunreachable-code on by default, in -Wall, or at

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,15 @@ def warn_unused_lambda_capture: Warning<"lambda capture %0 is not "
379379
"%select{used|required to be captured for this use}1">,
380380
InGroup<UnusedLambdaCapture>, DefaultIgnore;
381381

382+
def warn_reserved_extern_symbol: Warning<
383+
"identifier %0 is reserved because %select{"
384+
"<ERROR>|" // ReservedIdentifierStatus::NotReserved
385+
"it starts with '_' at global scope|"
386+
"it starts with '__'|"
387+
"it starts with '_' followed by a capital letter|"
388+
"it contains '__'}1">,
389+
InGroup<ReservedIdentifier>, DefaultIgnore;
390+
382391
def warn_parameter_size: Warning<
383392
"%0 is a large (%1 bytes) pass-by-value argument; "
384393
"pass it by reference instead ?">, InGroup<LargeByValueCopy>;

clang/include/clang/Basic/IdentifierTable.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ class LangOptions;
4040
class MultiKeywordSelector;
4141
class SourceLocation;
4242

43+
enum class ReservedIdentifierStatus {
44+
NotReserved = 0,
45+
StartsWithUnderscoreAtGlobalScope,
46+
StartsWithDoubleUnderscore,
47+
StartsWithUnderscoreFollowedByCapitalLetter,
48+
ContainsDoubleUnderscore,
49+
};
50+
4351
/// A simple pair of identifier info and location.
4452
using IdentifierLocPair = std::pair<IdentifierInfo *, SourceLocation>;
4553

@@ -385,14 +393,7 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
385393

386394
/// Determine whether \p this is a name reserved for the implementation (C99
387395
/// 7.1.3, C++ [lib.global.names]).
388-
bool isReservedName(bool doubleUnderscoreOnly = false) const {
389-
if (getLength() < 2)
390-
return false;
391-
const char *Name = getNameStart();
392-
return Name[0] == '_' &&
393-
(Name[1] == '_' ||
394-
(Name[1] >= 'A' && Name[1] <= 'Z' && !doubleUnderscoreOnly));
395-
}
396+
ReservedIdentifierStatus isReserved(const LangOptions &LangOpts) const;
396397

397398
/// Provide less than operator for lexicographical sorting.
398399
bool operator<(const IdentifierInfo &RHS) const {

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2590,6 +2590,8 @@ class Sema final {
25902590
SourceLocation Less,
25912591
SourceLocation Greater);
25922592

2593+
void warnOnReservedIdentifier(const NamedDecl *D);
2594+
25932595
Decl *ActOnDeclarator(Scope *S, Declarator &D);
25942596

25952597
NamedDecl *HandleDeclarator(Scope *S, Declarator &D,

clang/lib/AST/Decl.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,29 @@ bool NamedDecl::isLinkageValid() const {
10781078
return L == getCachedLinkage();
10791079
}
10801080

1081+
ReservedIdentifierStatus
1082+
NamedDecl::isReserved(const LangOptions &LangOpts) const {
1083+
const IdentifierInfo *II = getIdentifier();
1084+
if (!II)
1085+
if (const auto *FD = dyn_cast<FunctionDecl>(this))
1086+
II = FD->getLiteralIdentifier();
1087+
1088+
if (!II)
1089+
return ReservedIdentifierStatus::NotReserved;
1090+
1091+
ReservedIdentifierStatus Status = II->isReserved(LangOpts);
1092+
if (Status == ReservedIdentifierStatus::StartsWithUnderscoreAtGlobalScope) {
1093+
// Check if we're at TU level or not.
1094+
if (isa<ParmVarDecl>(this) || isTemplateParameter())
1095+
return ReservedIdentifierStatus::NotReserved;
1096+
const DeclContext *DC = getDeclContext()->getRedeclContext();
1097+
if (!DC->isTranslationUnit())
1098+
return ReservedIdentifierStatus::NotReserved;
1099+
}
1100+
1101+
return Status;
1102+
}
1103+
10811104
ObjCStringFormatFamily NamedDecl::getObjCFStringFormattingFamily() const {
10821105
StringRef name = getName();
10831106
if (name.empty()) return SFF_None;

clang/lib/Basic/IdentifierTable.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,39 @@ bool IdentifierInfo::isCPlusPlusKeyword(const LangOptions &LangOpts) const {
273273
return !isKeyword(LangOptsNoCPP);
274274
}
275275

276+
ReservedIdentifierStatus
277+
IdentifierInfo::isReserved(const LangOptions &LangOpts) const {
278+
StringRef Name = getName();
279+
280+
// '_' is a reserved identifier, but its use is so common (e.g. to store
281+
// ignored values) that we don't warn on it.
282+
if (Name.size() <= 1)
283+
return ReservedIdentifierStatus::NotReserved;
284+
285+
// [lex.name] p3
286+
if (Name[0] == '_') {
287+
288+
// Each name that begins with an underscore followed by an uppercase letter
289+
// or another underscore is reserved.
290+
if (Name[1] == '_')
291+
return ReservedIdentifierStatus::StartsWithDoubleUnderscore;
292+
293+
if ('A' <= Name[1] && Name[1] <= 'Z')
294+
return ReservedIdentifierStatus::
295+
StartsWithUnderscoreFollowedByCapitalLetter;
296+
297+
// This is a bit misleading: it actually means it's only reserved if we're
298+
// at global scope because it starts with an underscore.
299+
return ReservedIdentifierStatus::StartsWithUnderscoreAtGlobalScope;
300+
}
301+
302+
// Each name that contains a double underscore (__) is reserved.
303+
if (LangOpts.CPlusPlus && Name.contains("__"))
304+
return ReservedIdentifierStatus::ContainsDoubleUnderscore;
305+
306+
return ReservedIdentifierStatus::NotReserved;
307+
}
308+
276309
tok::PPKeywordKind IdentifierInfo::getPPKeywordID() const {
277310
// We use a perfect hash function here involving the length of the keyword,
278311
// the first and third character. For preprocessor ID's there are no

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,9 +4058,9 @@ void CGDebugInfo::EmitFuncDeclForCallSite(llvm::CallBase *CallOrInvoke,
40584058
if (CalleeDecl->getBuiltinID() != 0 || CalleeDecl->hasAttr<NoDebugAttr>() ||
40594059
getCallSiteRelatedAttrs() == llvm::DINode::FlagZero)
40604060
return;
4061-
if (const auto *Id = CalleeDecl->getIdentifier())
4062-
if (Id->isReservedName())
4063-
return;
4061+
if (CalleeDecl->isReserved(CGM.getLangOpts()) !=
4062+
ReservedIdentifierStatus::NotReserved)
4063+
return;
40644064

40654065
// If there is no DISubprogram attached to the function being called,
40664066
// create the one describing the function in order to have complete

clang/lib/Sema/SemaCodeComplete.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -739,18 +739,17 @@ getRequiredQualification(ASTContext &Context, const DeclContext *CurContext,
739739
// Filter out names reserved for the implementation if they come from a
740740
// system header.
741741
static bool shouldIgnoreDueToReservedName(const NamedDecl *ND, Sema &SemaRef) {
742-
const IdentifierInfo *Id = ND->getIdentifier();
743-
if (!Id)
744-
return false;
745-
742+
ReservedIdentifierStatus Status = ND->isReserved(SemaRef.getLangOpts());
746743
// Ignore reserved names for compiler provided decls.
747-
if (Id->isReservedName() && ND->getLocation().isInvalid())
744+
if ((Status != ReservedIdentifierStatus::NotReserved) &&
745+
(Status != ReservedIdentifierStatus::StartsWithUnderscoreAtGlobalScope) &&
746+
ND->getLocation().isInvalid())
748747
return true;
749748

750749
// For system headers ignore only double-underscore names.
751750
// This allows for system headers providing private symbols with a single
752751
// underscore.
753-
if (Id->isReservedName(/*doubleUnderscoreOnly=*/true) &&
752+
if (Status == ReservedIdentifierStatus::StartsWithDoubleUnderscore &&
754753
SemaRef.SourceMgr.isInSystemHeader(
755754
SemaRef.SourceMgr.getSpellingLoc(ND->getLocation())))
756755
return true;

clang/lib/Sema/SemaDecl.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,7 @@ void Sema::PushOnScopeChains(NamedDecl *D, Scope *S, bool AddToContext) {
15161516
} else {
15171517
IdResolver.AddDecl(D);
15181518
}
1519+
warnOnReservedIdentifier(D);
15191520
}
15201521

15211522
bool Sema::isDeclInScope(NamedDecl *D, DeclContext *Ctx, Scope *S,
@@ -5558,6 +5559,18 @@ static bool RebuildDeclaratorInCurrentInstantiation(Sema &S, Declarator &D,
55585559
return false;
55595560
}
55605561

5562+
void Sema::warnOnReservedIdentifier(const NamedDecl *D) {
5563+
// Avoid warning twice on the same identifier, and don't warn on redeclaration
5564+
// of system decl.
5565+
if (D->getPreviousDecl() || D->isImplicit())
5566+
return;
5567+
ReservedIdentifierStatus Status = D->isReserved(getLangOpts());
5568+
if (Status != ReservedIdentifierStatus::NotReserved &&
5569+
!Context.getSourceManager().isInSystemHeader(D->getLocation()))
5570+
Diag(D->getLocation(), diag::warn_reserved_extern_symbol)
5571+
<< D << static_cast<int>(Status);
5572+
}
5573+
55615574
Decl *Sema::ActOnDeclarator(Scope *S, Declarator &D) {
55625575
D.setFunctionDefinitionKind(FunctionDefinitionKind::Declaration);
55635576
Decl *Dcl = HandleDeclarator(S, D, MultiTemplateParamsArg());

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16778,6 +16778,8 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
1677816778
}
1677916779
}
1678016780

16781+
warnOnReservedIdentifier(ND);
16782+
1678116783
return ND;
1678216784
}
1678316785

clang/lib/Sema/SemaStmt.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,12 @@ Sema::ActOnLabelStmt(SourceLocation IdentLoc, LabelDecl *TheDecl,
542542
return SubStmt;
543543
}
544544

545+
ReservedIdentifierStatus Status = TheDecl->isReserved(getLangOpts());
546+
if (Status != ReservedIdentifierStatus::NotReserved &&
547+
!Context.getSourceManager().isInSystemHeader(IdentLoc))
548+
Diag(IdentLoc, diag::warn_reserved_extern_symbol)
549+
<< TheDecl << static_cast<int>(Status);
550+
545551
// Otherwise, things are good. Fill in the declaration and return it.
546552
LabelStmt *LS = new (Context) LabelStmt(IdentLoc, TheDecl, SubStmt);
547553
TheDecl->setStmt(LS);

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,9 @@ Sema::ActOnTemplateParameterList(unsigned Depth,
16771677
if (ExportLoc.isValid())
16781678
Diag(ExportLoc, diag::warn_template_export_unsupported);
16791679

1680+
for (NamedDecl *P : Params)
1681+
warnOnReservedIdentifier(P);
1682+
16801683
return TemplateParameterList::Create(
16811684
Context, TemplateLoc, LAngleLoc,
16821685
llvm::makeArrayRef(Params.data(), Params.size()),

clang/test/Preprocessor/macro-reserved.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
#define volatile // expected-warning {{keyword is hidden by macro definition}}
4747
#undef volatile
4848

49-
#pragma clang diagnostic warning "-Wreserved-id-macro"
49+
#pragma clang diagnostic warning "-Wreserved-macro-identifier"
5050

5151
#define switch if // expected-warning {{keyword is hidden by macro definition}}
5252
#define final 1

clang/test/Preprocessor/macro-reserved.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747
#define volatile // expected-warning {{keyword is hidden by macro definition}}
4848
#undef volatile
4949

50-
51-
#pragma clang diagnostic warning "-Wreserved-id-macro"
50+
#pragma clang diagnostic warning "-Wreserved-macro-identifier"
5251

5352
#define switch if // expected-warning {{keyword is hidden by macro definition}}
5453
#define final 1

clang/test/Sema/reserved-identifier.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -Wreserved-identifier -Wno-visibility %s
2+
3+
#define __oof foo__ // expected-warning {{macro name is a reserved identifier}}
4+
5+
int foo__bar() { return 0; } // no-warning
6+
static int _bar() { return 0; } // expected-warning {{identifier '_bar' is reserved because it starts with '_' at global scope}}
7+
static int _Bar() { return 0; } // expected-warning {{identifier '_Bar' is reserved because it starts with '_' followed by a capital letter}}
8+
int _foo() { return 0; } // expected-warning {{identifier '_foo' is reserved because it starts with '_' at global scope}}
9+
10+
// This one is explicitly skipped by -Wreserved-identifier
11+
void *_; // no-warning
12+
13+
void foo(unsigned int _Reserved) { // expected-warning {{identifier '_Reserved' is reserved because it starts with '_' followed by a capital letter}}
14+
unsigned int __1 = // expected-warning {{identifier '__1' is reserved because it starts with '__'}}
15+
_Reserved; // no-warning
16+
goto __reserved; // expected-warning {{identifier '__reserved' is reserved because it starts with '__'}}
17+
__reserved: // expected-warning {{identifier '__reserved' is reserved because it starts with '__'}}
18+
;
19+
}
20+
21+
void foot(unsigned int _not_reserved) {} // no-warning
22+
23+
enum __menu { // expected-warning {{identifier '__menu' is reserved because it starts with '__'}}
24+
__some, // expected-warning {{identifier '__some' is reserved because it starts with '__'}}
25+
_Other, // expected-warning {{identifier '_Other' is reserved because it starts with '_' followed by a capital letter}}
26+
_other // expected-warning {{identifier '_other' is reserved because it starts with '_' at global scope}}
27+
};
28+
29+
struct __babar { // expected-warning {{identifier '__babar' is reserved because it starts with '__'}}
30+
};
31+
32+
struct _Zebulon; // expected-warning {{identifier '_Zebulon' is reserved because it starts with '_' followed by a capital letter}}
33+
struct _Zebulon2 { // expected-warning {{identifier '_Zebulon2' is reserved because it starts with '_' followed by a capital letter}}
34+
} * p;
35+
struct _Zebulon3 *pp; // expected-warning {{identifier '_Zebulon3' is reserved because it starts with '_' followed by a capital letter}}
36+
37+
typedef struct {
38+
int _Field; // expected-warning {{identifier '_Field' is reserved because it starts with '_' followed by a capital letter}}
39+
int _field; // no-warning
40+
} _Typedef; // expected-warning {{identifier '_Typedef' is reserved because it starts with '_' followed by a capital letter}}
41+
42+
int foobar() {
43+
return foo__bar(); // no-warning
44+
}
45+
46+
struct _reserved { // expected-warning {{identifier '_reserved' is reserved because it starts with '_' at global scope}}
47+
int a;
48+
} cunf(void) {
49+
return (struct _reserved){1};
50+
}
51+
52+
// FIXME: According to clang declaration context layering, _preserved belongs to
53+
// the translation unit, so we emit a warning. It's unclear that's what the
54+
// standard mandate, but it's such a corner case we can live with it.
55+
void func(struct _preserved { int a; } r) {} // expected-warning {{identifier '_preserved' is reserved because it starts with '_' at global scope}}
56+
57+
extern char *_strdup(const char *); // expected-warning {{identifier '_strdup' is reserved because it starts with '_' at global scope}}
58+
59+
// Don't warn on redecleration
60+
extern char *_strdup(const char *); // no-warning
61+
62+
void ok() {
63+
void _ko(); // expected-warning {{identifier '_ko' is reserved because it starts with '_' at global scope}}
64+
extern int _ko_again; // expected-warning {{identifier '_ko_again' is reserved because it starts with '_' at global scope}}
65+
}

0 commit comments

Comments
 (0)