Warn if using elifdef & elifndef in not C2x & C++2b mode

This adds an extension warning when using the preprocessor conditionals
in a language mode they're not officially supported in, and an opt-in
warning for compatibility with previous standards.

Fixes #55306
Differential Revision: https://reviews.llvm.org/D125178
This commit is contained in:
Ken Matsui 2022-05-12 09:25:05 -04:00 committed by Aaron Ballman
parent 28a0b94d22
commit a1545f51a9
11 changed files with 161 additions and 21 deletions

View File

@ -217,6 +217,12 @@ Improvements to Clang's diagnostics
- Added the ``-Wgnu-line-marker`` diagnostic flag (grouped under the ``-Wgnu``
flag) which is a portability warning about use of GNU linemarker preprocessor
directives. Fixes `Issue 55067 <https://github.com/llvm/llvm-project/issues/55067>`_.
- Using ``#elifdef`` and ``#elifndef`` that are incompatible with C/C++
standards before C2x/C++2b are now warned via ``-pedantic``. Additionally,
on such language mode, ``-Wpre-c2x-compat`` and ``-Wpre-c++2b-compat``
diagnostic flags report a compatibility issue.
Fixes `Issue 55306 <https://github.com/llvm/llvm-project/issues/55306>`_.
Non-comprehensive list of changes in this release
-------------------------------------------------

View File

@ -698,6 +698,23 @@ def warn_cxx98_compat_pp_line_too_big : Warning<
"#line number greater than 32767 is incompatible with C++98">,
InGroup<CXX98CompatPedantic>, DefaultIgnore;
def warn_c2x_compat_pp_directive : Warning<
"use of a '#%select{<BUG IF SEEN>|elifdef|elifndef}0' directive "
"is incompatible with C standards before C2x">,
InGroup<CPre2xCompat>, DefaultIgnore;
def ext_c2x_pp_directive : ExtWarn<
"use of a '#%select{<BUG IF SEEN>|elifdef|elifndef}0' directive "
"is a C2x extension">,
InGroup<C2x>;
def warn_cxx2b_compat_pp_directive : Warning<
"use of a '#%select{<BUG IF SEEN>|elifdef|elifndef}0' directive "
"is incompatible with C++ standards before C++2b">,
InGroup<CXXPre2bCompat>, DefaultIgnore;
def ext_cxx2b_pp_directive : ExtWarn<
"use of a '#%select{<BUG IF SEEN>|elifdef|elifndef}0' directive "
"is a C++2b extension">,
InGroup<CXX2b>;
def err_pp_visibility_non_macro : Error<"no macro named %0">;
def err_pp_arc_cf_code_audited_syntax : Error<"expected 'begin' or 'end'">;

View File

@ -652,6 +652,17 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation HashTokenLoc,
PPConditionalInfo &CondInfo = CurPPLexer->peekConditionalLevel();
Token DirectiveToken = Tok;
// Warn if using `#elifdef` & `#elifndef` in not C2x & C++2b mode even
// if this branch is in a skipping block.
unsigned DiagID;
if (LangOpts.CPlusPlus)
DiagID = LangOpts.CPlusPlus2b ? diag::warn_cxx2b_compat_pp_directive
: diag::ext_cxx2b_pp_directive;
else
DiagID = LangOpts.C2x ? diag::warn_c2x_compat_pp_directive
: diag::ext_c2x_pp_directive;
Diag(Tok, DiagID) << (IsElifDef ? PED_Elifdef : PED_Elifndef);
// If this is a #elif with a #else before it, report the error.
if (CondInfo.FoundElse)
Diag(Tok, diag::pp_err_elif_after_else)
@ -3259,6 +3270,23 @@ void Preprocessor::HandleElifFamilyDirective(Token &ElifToken,
: PED_Elifndef;
++NumElse;
// Warn if using `#elifdef` & `#elifndef` in not C2x & C++2b mode.
switch (DirKind) {
case PED_Elifdef:
case PED_Elifndef:
unsigned DiagID;
if (LangOpts.CPlusPlus)
DiagID = LangOpts.CPlusPlus2b ? diag::warn_cxx2b_compat_pp_directive
: diag::ext_cxx2b_pp_directive;
else
DiagID = LangOpts.C2x ? diag::warn_c2x_compat_pp_directive
: diag::ext_c2x_pp_directive;
Diag(ElifToken, DiagID) << DirKind;
break;
default:
break;
}
// #elif directive in a non-skipping conditional... start skipping.
// We don't care what the condition is, because we will always skip it (since
// the block immediately before it was included).

View File

@ -40,6 +40,7 @@ const int z = UNSAFE_MACRO_2;
#ifdef baz
#elifdef UNSAFE_MACRO
// expected-warning@-1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// expected-warning@-2{{use of a '#elifdef' directive is a C2x extension}}
#endif
// Test that we diagnose on #elifndef.
@ -47,6 +48,7 @@ const int z = UNSAFE_MACRO_2;
#elifndef UNSAFE_MACRO
#endif
// expected-warning@-2{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// expected-warning@-3{{use of a '#elifndef' directive is a C2x extension}}
// FIXME: These cases are currently not handled because clang doesn't expand
// conditions on skipped #elif* blocks. See the FIXME notes in
@ -55,12 +57,14 @@ const int z = UNSAFE_MACRO_2;
#define frobble
#ifdef frobble
// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// not-expected-warning@+2{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// expected-warning@+1{{use of a '#elifndef' directive is a C2x extension}}
#elifndef UNSAFE_MACRO
#endif
#ifdef frobble
// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// not-expected-warning@+2{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}}
// expected-warning@+1{{use of a '#elifdef' directive is a C2x extension}}
#elifdef UNSAFE_MACRO
#endif

View File

@ -79,6 +79,7 @@ int main(int argc, char** argv) {
#ifdef baz
#elifdef foo
// expected-warning@-1{{macro 'foo' has been marked as deprecated}}
// expected-warning@-2{{use of a '#elifdef' directive is a C2x extension}}
#endif
// Test that we diagnose on #elifndef.
@ -86,18 +87,21 @@ int main(int argc, char** argv) {
#elifndef foo
#endif
// expected-warning@-2{{macro 'foo' has been marked as deprecated}}
// expected-warning@-3{{use of a '#elifndef' directive is a C2x extension}}
// FIXME: These cases are currently not handled because clang doesn't expand
// conditions on skipped #elif* blocks. See the FIXME notes in
// Preprocessor::SkipExcludedConditionalBlock.
#ifdef frobble
// not-expected-warning@+1{{macro 'foo' has been marked as deprecated}}
// not-expected-warning@+2{{macro 'foo' has been marked as deprecated}}
// expected-warning@+1{{use of a '#elifndef' directive is a C2x extension}}
#elifndef foo
#endif
#ifdef frobble
// not-expected-warning@+1{{macro 'foo' has been marked as deprecated}}
// not-expected-warning@+2{{macro 'foo' has been marked as deprecated}}
// expected-warning@+1{{use of a '#elifdef' directive is a C2x extension}}
#elifdef foo
#endif

View File

@ -1,24 +1,28 @@
// RUN: %clang_cc1 %s -Eonly -verify
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR
#error "did not expect to get here"
#endif
/* expected-error@+4 {{"got it"}} */
/* expected-error@+5 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR
#else
#error "got it"
#endif
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#elifndef BAR
#error "got it"
#endif
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#elifndef BAR
#error "got it"
@ -27,32 +31,37 @@
#endif
#define BAR
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR
#error "got it"
#endif
#undef BAR
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR // test that comments aren't an issue
#error "did not expect to get here"
#endif
/* expected-error@+4 {{"got it"}} */
/* expected-error@+5 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR // test that comments aren't an issue
#else
#error "got it"
#endif
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#elifndef BAR // test that comments aren't an issue
#error "got it"
#endif
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#elifndef BAR // test that comments aren't an issue
#error "got it"
@ -61,7 +70,8 @@
#endif
#define BAR
/* expected-error@+3 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef BAR // test that comments aren't an issue
#error "got it"
@ -69,7 +79,8 @@
#undef BAR
#define BAR
/* expected-error@+6 {{"got it"}} */
/* expected-error@+7 {{"got it"}} */
/* expected-warning@+3 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#error "did not expect to get here"
#elifndef BAR
@ -79,27 +90,33 @@
#endif
#undef BAR
/* expected-error@+3 {{#elifdef after #else}} */
/* expected-error@+4 {{#elifdef after #else}} */
/* expected-warning@+3 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#else
#elifdef BAR
#endif
/* expected-error@+3 {{#elifndef after #else}} */
/* expected-error@+4 {{#elifndef after #else}} */
/* expected-warning@+3 {{use of a '#elifndef' directive is a C2x extension}} */
#ifdef FOO
#else
#elifndef BAR
#endif
/* expected-warning@+1 {{use of a '#elifdef' directive is a C2x extension}} */
#elifdef FOO /* expected-error {{#elifdef without #if}} */
#endif /* expected-error {{#endif without #if}} */
/* expected-warning@+1 {{use of a '#elifndef' directive is a C2x extension}} */
#elifndef FOO /* expected-error {{#elifndef without #if}} */
#endif /* expected-error {{#endif without #if}} */
/* Note, we do not expect errors about the missing macro name in the skipped
blocks. This is consistent with #elif behavior. */
/* expected-error@+2 {{"got it"}} */
/* expected-error@+4 {{"got it"}} */
/* expected-warning@+4 {{use of a '#elifdef' directive is a C2x extension}} */
/* expected-warning@+4 {{use of a '#elifndef' directive is a C2x extension}} */
#ifndef FOO
#error "got it"
#elifdef

View File

@ -0,0 +1,59 @@
// For C
// RUN: %clang_cc1 -std=c99 -fsyntax-only -verify=pre-c2x-pedantic -pedantic %s
// RUN: %clang_cc1 -std=c2x -fsyntax-only -verify=pre-c2x-compat -Wpre-c2x-compat %s
// RUN: not %clang_cc1 -std=c99 -fsyntax-only -verify %s
// RUN: not %clang_cc1 -std=c2x -fsyntax-only -verify -pedantic %s
// RUN: not %clang_cc1 -std=c2x -fsyntax-only -verify %s
// For C++
// RUN: %clang_cc1 -x c++ -fsyntax-only -verify=pre-cpp2b-pedantic -pedantic %s
// RUN: %clang_cc1 -x c++ -std=c++2b -fsyntax-only -verify=pre-cpp2b-compat -Wpre-c++2b-compat %s
// RUN: not %clang_cc1 -x c++ -fsyntax-only -verify %s
// RUN: not %clang_cc1 -x c++ -std=c++2b -fsyntax-only -verify -pedantic %s
// RUN: not %clang_cc1 -x c++ -std=c++2b -fsyntax-only -verify %s
int x;
#if 1
#elifdef A // #1
#endif
// For C
// pre-c2x-pedantic-warning@#1 {{use of a '#elifdef' directive is a C2x extension}}
// pre-c2x-compat-warning@#1 {{use of a '#elifdef' directive is incompatible with C standards before C2x}}
// For C++
// pre-cpp2b-pedantic-warning@#1 {{use of a '#elifdef' directive is a C++2b extension}}
// pre-cpp2b-compat-warning@#1 {{use of a '#elifdef' directive is incompatible with C++ standards before C++2b}}
#if 1
#elifndef B // #2
#endif
// For C
// pre-c2x-pedantic-warning@#2 {{use of a '#elifndef' directive is a C2x extension}}
// pre-c2x-compat-warning@#2 {{use of a '#elifndef' directive is incompatible with C standards before C2x}}
// For C++
// pre-cpp2b-pedantic-warning@#2 {{use of a '#elifndef' directive is a C++2b extension}}
// pre-cpp2b-compat-warning@#2 {{use of a '#elifndef' directive is incompatible with C++ standards before C++2b}}
#if 0
#elifdef C
#endif
// For C
// pre-c2x-pedantic-warning@-3 {{use of a '#elifdef' directive is a C2x extension}}
// pre-c2x-compat-warning@-4 {{use of a '#elifdef' directive is incompatible with C standards before C2x}}
// For C++
// pre-cpp2b-pedantic-warning@-7 {{use of a '#elifdef' directive is a C++2b extension}}
// pre-cpp2b-compat-warning@-8 {{use of a '#elifdef' directive is incompatible with C++ standards before C++2b}}
#if 0
#elifndef D
#endif
// For C
// pre-c2x-pedantic-warning@-3 {{use of a '#elifndef' directive is a C2x extension}}
// pre-c2x-compat-warning@-4 {{use of a '#elifndef' directive is incompatible with C standards before C2x}}
// For C++
// pre-cpp2b-pedantic-warning@-7 {{use of a '#elifndef' directive is a C++2b extension}}
// pre-cpp2b-compat-warning@-8 {{use of a '#elifndef' directive is incompatible with C++ standards before C++2b}}

View File

@ -5,6 +5,7 @@ extern int x;
#if foo // expected-error {{'foo' is not defined, evaluates to 0}}
#endif
// expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}}
#ifdef foo
#elifdef foo
#endif
@ -14,6 +15,7 @@ extern int x;
// PR3938
// expected-warning@+3 {{use of a '#elifdef' directive is a C2x extension}}
#if 0
#ifdef D
#elifdef D

View File

@ -19,12 +19,14 @@
#if f(2
#endif
/* expected-error@+2 {{macro name missing}} */
/* expected-error@+3 {{macro name missing}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef
#endif
/* expected-error@+2 {{macro name must be an identifier}} */
/* expected-error@+3 {{macro name must be an identifier}} */
/* expected-warning@+2 {{use of a '#elifdef' directive is a C2x extension}} */
#ifdef FOO
#elifdef !
#endif

View File

@ -4,6 +4,7 @@
#ifdef defined
#elifdef defined
#endif
// expected-warning@-2 {{use of a '#elifdef' directive is a C2x extension}}

View File

@ -1,6 +1,6 @@
// RUN: %clang_cc1 %s -Eonly -verify -Wno-all -pedantic -std=c++20
// RUN: %clang_cc1 %s -Eonly -verify -Wno-all -pedantic -std=c++11
// RUN: %clang_cc1 -x c %s -Eonly -verify -Wno-all -pedantic -std=c99
// RUN: %clang_cc1 %s -Eonly -verify -Wno-all -Wno-c++2b-extensions -pedantic -std=c++20
// RUN: %clang_cc1 %s -Eonly -verify -Wno-all -Wno-c++2b-extensions -pedantic -std=c++11
// RUN: %clang_cc1 -x c %s -Eonly -verify -Wno-all -Wno-c2x-extensions -pedantic -std=c99
//expected-error@+1{{missing '('}}
#define V1(...) __VA_OPT__