Skip to content

Commit 85c8100

Browse files
authored
[C] Diagnose use of C++ keywords in C (#137234)
This adds a new diagnostic group, -Wc++-keyword, which is off by default and grouped under -Wc++-compat. The diagnostic catches use of C++ keywords in C code. This change additionally fixes an issue with -Wreserved-identifier not diagnosing use of reserved identifiers in function parameter lists in a function declaration which is not a definition. Fixes #21898
1 parent 3d4f979 commit 85c8100

File tree

11 files changed

+341
-12
lines changed

11 files changed

+341
-12
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ C Language Changes
154154
- Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
155155
diagnoses implicit conversion from ``void *`` to another pointer type as
156156
being incompatible with C++. (#GH17792)
157+
- Added ``-Wc++-keyword``, grouped under ``-Wc++-compat``, which diagnoses when
158+
a C++ keyword is used as an identifier in C. (#GH21898)
157159
- Added ``-Wc++-hidden-decl``, grouped under ``-Wc++-compat``, which diagnoses
158160
use of tag types which are visible in C but not visible in C++ due to scoping
159161
rules. e.g.,
@@ -482,6 +484,8 @@ Improvements to Clang's diagnostics
482484
- ``-Winitializer-overrides`` and ``-Wreorder-init-list`` are now grouped under
483485
the ``-Wc99-designator`` diagnostic group, as they also are about the
484486
behavior of the C99 feature as it was introduced into C++20. Fixes #GH47037
487+
- ``-Wreserved-identifier`` now fires on reserved parameter names in a function
488+
declaration which is not a definition.
485489

486490
Improvements to Clang's time-trace
487491
----------------------------------

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def C99Compat : DiagGroup<"c99-compat">;
157157
def C23Compat : DiagGroup<"c23-compat">;
158158
def : DiagGroup<"c2x-compat", [C23Compat]>;
159159

160+
def CppKeywordInC : DiagGroup<"c++-keyword">;
160161
def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
161162
def InitStringTooLongMissingNonString :
162163
DiagGroup<"unterminated-string-initialization">;
@@ -178,9 +179,9 @@ def ImplicitIntToEnumCast : DiagGroup<"implicit-int-enum-cast",
178179
[ImplicitEnumEnumCast]>;
179180
def TentativeDefnCompat : DiagGroup<"tentative-definition-compat">;
180181
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit,
181-
ImplicitIntToEnumCast, HiddenCppDecl,
182-
InitStringTooLongForCpp,
183-
TentativeDefnCompat,
182+
ImplicitIntToEnumCast, CppKeywordInC,
183+
HiddenCppDecl, TentativeDefnCompat,
184+
InitStringTooLongForCpp,
184185
DuplicateDeclSpecifier]>;
185186

186187
def ExternCCompat : DiagGroup<"extern-c-compat">;

clang/include/clang/Basic/DiagnosticLexKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@ def warn_pp_macro_is_reserved_attribute_id : Warning<
421421
def warn_pp_objc_macro_redef_ignored : Warning<
422422
"ignoring redefinition of Objective-C qualifier macro">,
423423
InGroup<DiagGroup<"objc-macro-redefinition">>;
424+
def warn_pp_identifier_is_cpp_keyword : Warning<
425+
"identifier %0 conflicts with a C++ keyword">,
426+
InGroup<CppKeywordInC>, DefaultIgnore;
424427

425428
def pp_invalid_string_literal : Warning<
426429
"invalid string literal, ignoring final '\\'">;

clang/include/clang/Basic/IdentifierTable.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
195195
LLVM_PREFERRED_TYPE(bool)
196196
unsigned IsFinal : 1;
197197

198-
// 22 bits left in a 64-bit word.
198+
// True if this identifier would be a keyword in C++ mode.
199+
LLVM_PREFERRED_TYPE(bool)
200+
unsigned IsKeywordInCpp : 1;
201+
202+
// 21 bits left in a 64-bit word.
199203

200204
// Managed by the language front-end.
201205
void *FETokenInfo = nullptr;
@@ -212,7 +216,7 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
212216
IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false),
213217
RevertedTokenID(false), OutOfDate(false), IsModulesImport(false),
214218
IsMangledOpenMPVariantName(false), IsDeprecatedMacro(false),
215-
IsRestrictExpansion(false), IsFinal(false) {}
219+
IsRestrictExpansion(false), IsFinal(false), IsKeywordInCpp(false) {}
216220

217221
public:
218222
IdentifierInfo(const IdentifierInfo &) = delete;
@@ -444,6 +448,10 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
444448
}
445449
bool isCPlusPlusOperatorKeyword() const { return IsCPPOperatorKeyword; }
446450

451+
/// Return true if this identifier would be a keyword in C++ mode.
452+
bool IsKeywordInCPlusPlus() const { return IsKeywordInCpp; }
453+
void setIsKeywordInCPlusPlus(bool Val = true) { IsKeywordInCpp = Val; }
454+
447455
/// Return true if this token is a keyword in the specified language.
448456
bool isKeyword(const LangOptions &LangOpts) const;
449457

@@ -462,6 +470,7 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
462470
/// If this returns false, we know that HandleIdentifier will not affect
463471
/// the token.
464472
bool isHandleIdentifierCase() const { return NeedsHandleIdentifier; }
473+
void setHandleIdentifierCase(bool Val = true) { NeedsHandleIdentifier = Val; }
465474

466475
/// Return true if the identifier in its current state was loaded
467476
/// from an AST file.

clang/include/clang/Basic/TokenKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,7 @@ ANNOTATION(embed)
10421042
#undef TYPE_TRAIT_2
10431043
#undef TYPE_TRAIT_1
10441044
#undef TYPE_TRAIT
1045+
#undef MODULES_KEYWORD
10451046
#undef CXX20_KEYWORD
10461047
#undef CXX11_KEYWORD
10471048
#undef KEYWORD

clang/lib/Basic/IdentifierTable.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,32 @@ static KeywordStatus getKeywordStatus(const LangOptions &LangOpts,
250250
return CurStatus;
251251
}
252252

253+
static bool IsKeywordInCpp(unsigned Flags) {
254+
while (Flags != 0) {
255+
unsigned CurFlag = Flags & ~(Flags - 1);
256+
Flags = Flags & ~CurFlag;
257+
switch (static_cast<TokenKey>(CurFlag)) {
258+
case KEYCXX:
259+
case KEYCXX11:
260+
case KEYCXX20:
261+
case BOOLSUPPORT:
262+
case WCHARSUPPORT:
263+
case CHAR8SUPPORT:
264+
return true;
265+
default:
266+
break; // Go to the next flag, try again.
267+
}
268+
}
269+
return false;
270+
}
271+
272+
static void MarkIdentifierAsKeywordInCpp(IdentifierTable &Table,
273+
StringRef Name) {
274+
IdentifierInfo &II = Table.get(Name, tok::identifier);
275+
II.setIsKeywordInCPlusPlus();
276+
II.setHandleIdentifierCase();
277+
}
278+
253279
/// AddKeyword - This method is used to associate a token ID with specific
254280
/// identifiers because they are language keywords. This causes the lexer to
255281
/// automatically map matching identifiers to specialized token codes.
@@ -258,8 +284,18 @@ static void AddKeyword(StringRef Keyword,
258284
const LangOptions &LangOpts, IdentifierTable &Table) {
259285
KeywordStatus AddResult = getKeywordStatus(LangOpts, Flags);
260286

261-
// Don't add this keyword if disabled in this language.
262-
if (AddResult == KS_Disabled) return;
287+
// Don't add this keyword if disabled in this language and isn't otherwise
288+
// special.
289+
if (AddResult == KS_Disabled) {
290+
// We do not consider any identifiers to be C++ keywords when in
291+
// Objective-C because @ effectively introduces a custom grammar where C++
292+
// keywords can be used (and similar for selectors). We could enable this
293+
// for Objective-C, but it would require more logic to ensure we do not
294+
// issue compatibility diagnostics in these cases.
295+
if (!LangOpts.ObjC && IsKeywordInCpp(Flags))
296+
MarkIdentifierAsKeywordInCpp(Table, Keyword);
297+
return;
298+
}
263299

264300
IdentifierInfo &Info =
265301
Table.get(Keyword, AddResult == KS_Future ? tok::identifier : TokenCode);
@@ -304,9 +340,11 @@ void IdentifierTable::AddKeywords(const LangOptions &LangOpts) {
304340
#define ALIAS(NAME, TOK, FLAGS) \
305341
AddKeyword(StringRef(NAME), tok::kw_ ## TOK, \
306342
FLAGS, LangOpts, *this);
307-
#define CXX_KEYWORD_OPERATOR(NAME, ALIAS) \
308-
if (LangOpts.CXXOperatorNames) \
309-
AddCXXOperatorKeyword(StringRef(#NAME), tok::ALIAS, *this);
343+
#define CXX_KEYWORD_OPERATOR(NAME, ALIAS) \
344+
if (LangOpts.CXXOperatorNames) \
345+
AddCXXOperatorKeyword(StringRef(#NAME), tok::ALIAS, *this); \
346+
else \
347+
MarkIdentifierAsKeywordInCpp(*this, StringRef(#NAME));
310348
#define OBJC_AT_KEYWORD(NAME) \
311349
if (LangOpts.ObjC) \
312350
AddObjCKeyword(StringRef(#NAME), tok::objc_##NAME, *this);

clang/lib/Lex/Preprocessor.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,11 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) {
834834
II.setIsFutureCompatKeyword(false);
835835
}
836836

837+
// If this identifier would be a keyword in C++, diagnose as a compatibility
838+
// issue.
839+
if (II.IsKeywordInCPlusPlus() && !DisableMacroExpansion)
840+
Diag(Identifier, diag::warn_pp_identifier_is_cpp_keyword) << &II;
841+
837842
// If this is an extension token, diagnose its use.
838843
// We avoid diagnosing tokens that originate from macro definitions.
839844
// FIXME: This warning is disabled in cases where it shouldn't be,

clang/lib/Sema/SemaDecl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@
5959
#include "clang/Sema/SemaWasm.h"
6060
#include "clang/Sema/Template.h"
6161
#include "llvm/ADT/STLForwardCompat.h"
62+
#include "llvm/ADT/SmallPtrSet.h"
6263
#include "llvm/ADT/SmallString.h"
6364
#include "llvm/ADT/StringExtras.h"
65+
#include "llvm/ADT/StringSwitch.h"
6466
#include "llvm/Support/SaveAndRestore.h"
6567
#include "llvm/TargetParser/Triple.h"
6668
#include <algorithm>
@@ -10424,6 +10426,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1042410426
// Finally, we know we have the right number of parameters, install them.
1042510427
NewFD->setParams(Params);
1042610428

10429+
// If this declarator is a declaration and not a definition, its parameters
10430+
// will not be pushed onto a scope chain. That means we will not issue any
10431+
// reserved identifier warnings for the declaration, but we will for the
10432+
// definition. Handle those here.
10433+
if (!D.isFunctionDefinition()) {
10434+
for (const ParmVarDecl *PVD : Params)
10435+
warnOnReservedIdentifier(PVD);
10436+
}
10437+
1042710438
if (D.getDeclSpec().isNoreturnSpecified())
1042810439
NewFD->addAttr(
1042910440
C11NoReturnAttr::Create(Context, D.getDeclSpec().getNoreturnSpecLoc()));

clang/test/OpenMP/assumes_messages.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@
5959
#pragma omp begin assumes ext // expected-warning {{valid begin assumes clauses start with 'ext_', 'absent', 'contains', 'holds', 'no_openmp', 'no_openmp_routines', 'no_openmp_constructs', 'no_parallelism'; token will be ignored}}
6060
#pragma omp end assumes
6161

62-
#pragma omp assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}} expected-note {{the ignored tokens spans until here}}
63-
#pragma omp begin assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}} expected-note {{the ignored tokens spans until here}}
62+
// FIXME: We should be getting an expected note about where the span of ignored
63+
// tokens ends. However, error recovery ends up lexing the 'not' token,
64+
// emitting a (silenced) diagnostic about use of a C++ keyword in C, and the
65+
// note gets associated with *that* (silenced) diagnostic. This is an existing
66+
// issue that also happens with error recovery of reserved identifiers or
67+
// extension tokens, but is unfortunate nonetheless.
68+
#pragma omp assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}}
69+
#pragma omp begin assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}}
6470
#pragma omp end assumes
6571

6672
#pragma omp end assumes // expected-error {{'#pragma omp end assumes' with no matching '#pragma omp begin assumes'}}

0 commit comments

Comments
 (0)