diff options
Diffstat (limited to 'clang')
-rw-r--r-- | clang/include/clang/Basic/DiagnosticGroups.td | 1 | ||||
-rw-r--r-- | clang/include/clang/Basic/DiagnosticLexKinds.td | 7 | ||||
-rw-r--r-- | clang/lib/Lex/PPExpressions.cpp | 49 | ||||
-rw-r--r-- | clang/test/Lexer/cxx-features.cpp | 1 | ||||
-rw-r--r-- | clang/test/Preprocessor/expr_define_expansion.c | 26 |
5 files changed, 80 insertions, 4 deletions
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 2e4e57b63b8..a31cfdb2a93 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -204,6 +204,7 @@ def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">; def DanglingElse: DiagGroup<"dangling-else">; def DanglingField : DiagGroup<"dangling-field">; def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">; +def ExpansionToUndefined : DiagGroup<"expansion-to-undefined">; def FlagEnum : DiagGroup<"flag-enum">; def IncrementBool : DiagGroup<"increment-bool", [DeprecatedIncrementBool]>; def InfiniteRecursion : DiagGroup<"infinite-recursion">; diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 2fc9664f49e..04ae1a4323f 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -658,6 +658,13 @@ def warn_header_guard : Warning< def note_header_guard : Note< "%0 is defined here; did you mean %1?">; +def warn_defined_in_object_type_macro : Warning< + "macro expansion producing 'defined' has undefined behavior">, + InGroup<ExpansionToUndefined>; +def warn_defined_in_function_type_macro : Extension< + "macro expansion producing 'defined' has undefined behavior">, + InGroup<ExpansionToUndefined>; + let CategoryName = "Nullability Issue" in { def err_pp_assume_nonnull_syntax : Error<"expected 'begin' or 'end'">; diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp index c40598c0675..6603a17cbd4 100644 --- a/clang/lib/Lex/PPExpressions.cpp +++ b/clang/lib/Lex/PPExpressions.cpp @@ -140,6 +140,51 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT, PP.LexNonComment(PeekTok); } + // [cpp.cond]p4: + // Prior to evaluation, macro invocations in the list of preprocessing + // tokens that will become the controlling constant expression are replaced + // (except for those macro names modified by the 'defined' unary operator), + // just as in normal text. If the token 'defined' is generated as a result + // of this replacement process or use of the 'defined' unary operator does + // not match one of the two specified forms prior to macro replacement, the + // behavior is undefined. + // This isn't an idle threat, consider this program: + // #define FOO + // #define BAR defined(FOO) + // #if BAR + // ... + // #else + // ... + // #endif + // clang and gcc will pick the #if branch while Visual Studio will take the + // #else branch. Emit a warning about this undefined behavior. + if (beginLoc.isMacroID()) { + bool IsFunctionTypeMacro = + PP.getSourceManager() + .getSLocEntry(PP.getSourceManager().getFileID(beginLoc)) + .getExpansion() + .isFunctionMacroExpansion(); + // For object-type macros, it's easy to replace + // #define FOO defined(BAR) + // with + // #if defined(BAR) + // #define FOO 1 + // #else + // #define FOO 0 + // #endif + // and doing so makes sense since compilers handle this differently in + // practice (see example further up). But for function-type macros, + // there is no good way to write + // # define FOO(x) (defined(M_ ## x) && M_ ## x) + // in a different way, and compilers seem to agree on how to behave here. + // So warn by default on object-type macros, but only warn in -pedantic + // mode on function-type macros. + if (IsFunctionTypeMacro) + PP.Diag(beginLoc, diag::warn_defined_in_function_type_macro); + else + PP.Diag(beginLoc, diag::warn_defined_in_object_type_macro); + } + // Invoke the 'defined' callback. if (PPCallbacks *Callbacks = PP.getPPCallbacks()) { Callbacks->Defined(macroToken, Macro, @@ -177,8 +222,8 @@ static bool EvaluateValue(PPValue &Result, Token &PeekTok, DefinedTracker &DT, if (IdentifierInfo *II = PeekTok.getIdentifierInfo()) { // Handle "defined X" and "defined(X)". if (II->isStr("defined")) - return(EvaluateDefined(Result, PeekTok, DT, ValueLive, PP)); - + return EvaluateDefined(Result, PeekTok, DT, ValueLive, PP); + // 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, unless it is the C++ keyword "true", in which case it diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index 6c4a092b1c4..b0f382ec253 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -6,6 +6,7 @@ // expected-no-diagnostics +// FIXME using `defined` in a macro has undefined behavior. #if __cplusplus < 201103L #define check(macro, cxx98, cxx11, cxx1y) cxx98 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx98 #elif __cplusplus < 201304L diff --git a/clang/test/Preprocessor/expr_define_expansion.c b/clang/test/Preprocessor/expr_define_expansion.c index 3e5a2c4b0e6..23cb4355eb8 100644 --- a/clang/test/Preprocessor/expr_define_expansion.c +++ b/clang/test/Preprocessor/expr_define_expansion.c @@ -1,6 +1,28 @@ -// RUN: %clang_cc1 %s -E -CC -pedantic -verify -// expected-no-diagnostics +// RUN: %clang_cc1 %s -E -CC -verify +// RUN: %clang_cc1 %s -E -CC -DPEDANTIC -pedantic -verify #define FOO && 1 #if defined FOO FOO #endif + +#define A +#define B defined(A) +#if B // expected-warning{{macro expansion producing 'defined' has undefined behavior}} +#endif + +#define m_foo +#define TEST(a) (defined(m_##a) && a) + +#if defined(PEDANTIC) +// expected-warning@+4{{macro expansion producing 'defined' has undefined behavior}} +#endif + +// This shouldn't warn by default, only with pedantic: +#if TEST(foo) +#endif + + +// Only one diagnostic for this case: +#define INVALID defined( +#if INVALID // expected-error{{macro name missing}} +#endif |