diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -105,6 +105,8 @@ def FrameAddress : DiagGroup<"frame-address">; def DoublePromotion : DiagGroup<"double-promotion">; def EnumTooLarge : DiagGroup<"enum-too-large">; +def Undefined : DiagGroup<"undef">; +def UndefinedPrefix : DiagGroup<"undef-prefix", [Undefined]>; def UnsupportedNan : DiagGroup<"unsupported-nan">; def UnsupportedAbs : DiagGroup<"unsupported-abs">; def UnsupportedCB : DiagGroup<"unsupported-cb">; diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -311,7 +311,7 @@ InGroup>; def warn_pp_undef_identifier : Warning< "%0 is not defined, evaluates to 0">, - InGroup>, DefaultIgnore; + InGroup, DefaultIgnore; def warn_pp_ambiguous_macro : Warning< "ambiguous expansion of macro %0">, InGroup; def note_pp_ambiguous_macro_chosen : Note< diff --git a/clang/include/clang/Basic/DiagnosticOptions.h b/clang/include/clang/Basic/DiagnosticOptions.h --- a/clang/include/clang/Basic/DiagnosticOptions.h +++ b/clang/include/clang/Basic/DiagnosticOptions.h @@ -98,6 +98,10 @@ /// prefixes removed. std::vector Warnings; + /// The list of prefixes from -Wundef-prefix=... used to generate warnings + /// for undefined macros. + std::vector UndefPrefixes; + /// The list of -R... options used to alter the diagnostic mappings, with the /// prefixes removed. std::vector Remarks; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -483,6 +483,14 @@ def Wp_COMMA : CommaJoined<["-"], "Wp,">, HelpText<"Pass the comma separated arguments in to the preprocessor">, MetaVarName<"">, Group; +def Wundef_prefix_EQ : Joined<["-"], "Wundef-prefix=">, Group, + Flags<[CC1Option, CoreOption, HelpHidden]>, MetaVarName<"">, + HelpText<"Enable warnings for undefined macros with a prefix in the comma separated list ">; +// Rely on the implementation that a Flag alias for a Joined option is provided a default argument +// of an empty string (""). So we have -Wundef => -Wundef-prefix="" +def Wundef: Flag<["-"], "Wundef">, Group, Flags<[CC1Option, CoreOption, HelpHidden]>, + Alias, + HelpText<"Enable warnings for undefined macros">; def Wwrite_strings : Flag<["-"], "Wwrite-strings">, Group, Flags<[CC1Option, HelpHidden]>; def Wno_write_strings : Flag<["-"], "Wno-write-strings">, Group, Flags<[CC1Option, HelpHidden]>; def W_Joined : Joined<["-"], "W">, Group, Flags<[CC1Option, CoreOption]>, diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1688,6 +1688,16 @@ } Opts.MessageLength = getLastArgIntValue(Args, OPT_fmessage_length_EQ, 0, Diags); + + for (const auto *A : Args.filtered(OPT_Wundef_prefix_EQ)) { + SmallVector Prefixes; + // Keep empty strings to allow '-Wundef' (aliased to '-Wundef-prefix=""') + // to generate diagnostics for all undefined macros. + StringRef{A->getValue()}.split(Prefixes, ","); + for (const auto &P : Prefixes) + Opts.UndefPrefixes.push_back(P.str()); + } + addDiagnosticArgs(Args, OPT_W_Group, OPT_W_value_Group, Opts.Warnings); addDiagnosticArgs(Args, OPT_R_Group, OPT_R_value_Group, Opts.Remarks); diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp --- a/clang/lib/Lex/PPExpressions.cpp +++ b/clang/lib/Lex/PPExpressions.cpp @@ -15,7 +15,6 @@ // //===----------------------------------------------------------------------===// -#include "clang/Lex/Preprocessor.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" @@ -26,8 +25,10 @@ #include "clang/Lex/LiteralSupport.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Lex/Token.h" #include "llvm/ADT/APSInt.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ErrorHandling.h" @@ -251,8 +252,24 @@ // If this identifier isn't 'defined' or one of the special // preprocessor keywords and it wasn't macro expanded, it turns // into a simple 0 - if (ValueLive) - PP.Diag(PeekTok, diag::warn_pp_undef_identifier) << II; + if (ValueLive) { + const DiagnosticOptions &diagOptions = + PP.getDiagnostics().getDiagnosticOptions(); + const StringRef IdentifierName = II->getName(); + // Check whether "-Werror=undef" is present. + // "-Werror=undef" implies "-Wundef" but does not add an empty + // string to UndefPrefixes as an explicit "-Wundef" does. + // This check ensures that "-Werror=undef" emits errors for all + // undefined macros. + const bool hasWerrorEQundef = + llvm::is_contained(diagOptions.Warnings, "error=undef"); + if (hasWerrorEQundef || + llvm::any_of(diagOptions.UndefPrefixes, + [&IdentifierName](const std::string &Prefix) { + return IdentifierName.startswith(Prefix); + })) + PP.Diag(PeekTok, diag::warn_pp_undef_identifier) << II; + } Result.Val = 0; Result.Val.setIsUnsigned(false); // "0" is signed intmax_t 0. Result.setIdentifier(II); diff --git a/clang/test/Preprocessor/warn-macro-undef.c b/clang/test/Preprocessor/warn-macro-undef.c new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/warn-macro-undef.c @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 %s -Eonly -Wundef -verify=undef +// RUN: %clang_cc1 %s -Eonly -Wundef-prefix=A,BC -verify=undef-prefix +// RUN: %clang_cc1 %s -Eonly -Wundef -Wundef-prefix=A,BC -verify=both +// RUN: %clang_cc1 %s -Eonly -Werror=undef -verify=undef-error +// RUN: %clang_cc1 %s -Eonly -Werror=undef-prefix -Wundef-prefix=A,BC -verify=undef-prefix-error +// RUN: %clang_cc1 %s -Eonly -Werror=undef -Wundef-prefix=A,BC -verify=both-error + +extern int x; + +#if AB // #1 +#endif +// undef-warning@#1 {{'AB' is not defined, evaluates to 0}} +// undef-prefix-warning@#1 {{'AB' is not defined, evaluates to 0}} +// both-warning@#1 {{'AB' is not defined, evaluates to 0}} +// undef-error-error@#1 {{'AB' is not defined, evaluates to 0}} +// undef-prefix-error-error@#1 {{'AB' is not defined, evaluates to 0}} +// both-error-error@#1 {{'AB' is not defined, evaluates to 0}} + +#if B // #2 +#endif +// undef-warning@#2 {{'B' is not defined, evaluates to 0}} +// no warning for undef-prefix +// both-warning@#2 {{'B' is not defined, evaluates to 0}} +// undef-error-error@#2 {{'B' is not defined, evaluates to 0}} +// no error for undef-prefix +// both-error-error@#2 {{'B' is not defined, evaluates to 0}} + +#define BC 0 +#if BC // no warning/error +#endif + +#undef BC +#if BC // #3 +#endif +// undef-warning@#3 {{'BC' is not defined, evaluates to 0}} +// undef-prefix-warning@#3 {{'BC' is not defined, evaluates to 0}} +// both-warning@#3 {{'BC' is not defined, evaluates to 0}} +// undef-error-error@#3 {{'BC' is not defined, evaluates to 0}} +// undef-prefix-error-error@#3 {{'BC' is not defined, evaluates to 0}} +// both-error-error@#3 {{'BC' is not defined, evaluates to 0}} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundef" + +#if A // no warning/error +#endif + +#pragma clang diagnostic pop + +#if A // #4 +#endif +// undef-warning@#4 {{'A' is not defined, evaluates to 0}} +// undef-prefix-warning@#4 {{'A' is not defined, evaluates to 0}} +// both-warning@#4 {{'A' is not defined, evaluates to 0}} +// undef-error-error@#4 {{'A' is not defined, evaluates to 0}} +// undef-prefix-error-error@#4 {{'A' is not defined, evaluates to 0}} +// both-error-error@#4 {{'A' is not defined, evaluates to 0}}