https://github.com/benlangmuir created https://github.com/llvm/llvm-project/pull/200458
Preserve the necessary whitespace between the macro name and the body when an object-like macro starts with lparen. Otherwise it accidentally turns into an invalid function-like macro. Incidentally extend some tests to verify the resulting printed directives are valid preprocessing input. >From 5fe28ef8a4145b41ccf46fd437b6bd10e57b258f Mon Sep 17 00:00:00 2001 From: Ben Langmuir <[email protected]> Date: Thu, 28 May 2026 15:11:13 -0700 Subject: [PATCH] [clang][deps] Fix printing of object-like macros from dep directives Preserve the necessary whitespace between the macro name and the body when an object-like macro starts with lparen. Otherwise it accidentally turns into an invalid function-like macro. --- clang/lib/Lex/DependencyDirectivesScanner.cpp | 30 ++++++--- ...minimize_source_to_dependency_directives.c | 1 + ...to_dependency_directives_function_macros.c | 66 +++++++++++++++++++ ..._source_to_dependency_directives_pragmas.c | 8 ++- ..._source_to_dependency_directives_utf8bom.c | 4 +- .../Lex/DependencyDirectivesScannerTest.cpp | 19 ++++++ 6 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 clang/test/Lexer/minimize_source_to_dependency_directives_function_macros.c diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index ede5d49860fa4..ffddf67f39456 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -1084,34 +1084,44 @@ void clang::printDependencyDirectivesAsSource( ArrayRef<dependency_directives_scan::Directive> Directives, llvm::raw_ostream &OS) { // Add a space separator where it is convenient for testing purposes. - auto needsSpaceSeparator = - [](tok::TokenKind Prev, - const dependency_directives_scan::Token &Tok) -> bool { - if (Prev == Tok.Kind) + auto needsSpaceSeparator = [](const dependency_directives_scan::Token &Prev, + const dependency_directives_scan::Token &Tok, + DirectiveKind Kind, unsigned TokIdx) -> bool { + if (Prev.Kind == Tok.Kind) return !Tok.isOneOf(tok::l_paren, tok::r_paren, tok::l_square, tok::r_square); - if (Prev == tok::raw_identifier && + if (Prev.Kind == tok::raw_identifier && Tok.isOneOf(tok::hash, tok::numeric_constant, tok::string_literal, tok::char_constant, tok::header_name)) return true; - if (Prev == tok::r_paren && + if (Prev.Kind == tok::r_paren && Tok.isOneOf(tok::raw_identifier, tok::hash, tok::string_literal, tok::char_constant, tok::unknown)) return true; - if (Prev == tok::comma && + if (Prev.Kind == tok::comma && Tok.isOneOf(tok::l_paren, tok::string_literal, tok::less)) return true; + // For object-like macros that begin with an lparen, preserve a space + // between the name and the body to avoid it being converted to a + // function-like macro. + constexpr unsigned FirstBodyIdx = 3; // `#`, `define`, <NAME>, <BODY...> + if (Kind == pp_define && TokIdx == FirstBodyIdx && + Prev.is(tok::raw_identifier) && Tok.is(tok::l_paren) && + Tok.Offset > Prev.getEnd()) + return true; return false; }; for (const dependency_directives_scan::Directive &Directive : Directives) { if (Directive.Kind == tokens_present_before_eof) OS << "<TokBeforeEOF>"; - std::optional<tok::TokenKind> PrevTokenKind; + const dependency_directives_scan::Token *PrevTok = nullptr; + unsigned TokIdx = 0; for (const dependency_directives_scan::Token &Tok : Directive.Tokens) { - if (PrevTokenKind && needsSpaceSeparator(*PrevTokenKind, Tok)) + if (PrevTok && needsSpaceSeparator(*PrevTok, Tok, Directive.Kind, TokIdx)) OS << ' '; - PrevTokenKind = Tok.Kind; + PrevTok = &Tok; + ++TokIdx; OS << Source.slice(Tok.Offset, Tok.getEnd()); } } diff --git a/clang/test/Frontend/minimize_source_to_dependency_directives.c b/clang/test/Frontend/minimize_source_to_dependency_directives.c index 39f608b264a6a..24ed8ec6b7264 100644 --- a/clang/test/Frontend/minimize_source_to_dependency_directives.c +++ b/clang/test/Frontend/minimize_source_to_dependency_directives.c @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -print-dependency-directives-minimized-source %s > %t // RUN: echo END. >> %t // RUN: FileCheck < %t %s +// RUN: %clang_cc1 -Eonly %t #ifdef FOO #include "a.h" diff --git a/clang/test/Lexer/minimize_source_to_dependency_directives_function_macros.c b/clang/test/Lexer/minimize_source_to_dependency_directives_function_macros.c new file mode 100644 index 0000000000000..50786b7b80e15 --- /dev/null +++ b/clang/test/Lexer/minimize_source_to_dependency_directives_function_macros.c @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -print-dependency-directives-minimized-source %s > %t +// RUN: %clang_cc1 -E -dM %t | FileCheck %s + +// The conditional defines below all rely on the printer preserving the macro +// definitions and #if conditions accurately. + +#define ADD(x, y) ((x) + (y)) +#define SHL(x, n) ((x) << (n)) +#define HAS(x) (x) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define BAR() 42 + +#if ADD(1, 2) > 0 +#define POSITIVE_SUM +// CHECK-DAG: #define POSITIVE_SUM +#endif + +#if ADD(1, 2) == 3 && SHL(1, 3) == 8 +#define MATH_OK +// CHECK-DAG: #define MATH_OK +#endif + +#if HAS(0) || HAS(1) +#define HAS_EITHER +// CHECK-DAG: #define HAS_EITHER +#endif + +#if !ADD(0, 0) +#define NEGATED +// CHECK-DAG: #define NEGATED +#endif + +#if 1 + HAS(2) +#define LEADING_NUM +// CHECK-DAG: #define LEADING_NUM +#endif + +#if BAR() + BAR() == 84 +#define EMPTY_ARGS +// CHECK-DAG: #define EMPTY_ARGS +#endif + +#if MIN(1, 2) == 1 +#define MIN_OK +// CHECK-DAG: #define MIN_OK +#endif + +#if HAS(1) - HAS(2) < 0 +#define SUB_OK +// CHECK-DAG: #define SUB_OK +#endif + +#if HAS(0xff) & 0xf0 +#define BITWISE +// CHECK-DAG: #define BITWISE +#endif + +#if HAS(1) << 2 | HAS(2) +#define SHIFT_OR +// CHECK-DAG: #define SHIFT_OR +#endif + +#if defined(FOO) && ADD(1, 2) + ADD(3, 4) > 5 +#define COMPLEX_BUT_FALSE +// CHECK-NOT: #define COMPLEX_BUT_FALSE +#endif diff --git a/clang/test/Lexer/minimize_source_to_dependency_directives_pragmas.c b/clang/test/Lexer/minimize_source_to_dependency_directives_pragmas.c index 0971649caf673..3a4392e34cd07 100644 --- a/clang/test/Lexer/minimize_source_to_dependency_directives_pragmas.c +++ b/clang/test/Lexer/minimize_source_to_dependency_directives_pragmas.c @@ -1,5 +1,7 @@ // Test that the required #pragma directives are minimized -// RUN: %clang_cc1 -print-dependency-directives-minimized-source %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -print-dependency-directives-minimized-source %s > %t 2>&1 +// RUN: FileCheck %s -input-file %t +// RUN: %clang_cc1 -Eonly %t #pragma once @@ -11,11 +13,15 @@ // pragmas required in the minimized source. #pragma push_macro( "MYMACRO" ) #pragma pop_macro("MYMACRO") +#if IMPORT #pragma clang module import mymodule +#endif #pragma include_alias(<string>, "mystring.h") // CHECK: #pragma once // CHECK-NEXT: #pragma push_macro("MYMACRO") // CHECK-NEXT: #pragma pop_macro("MYMACRO") +// CHECK-NEXT: #if IMPORT // CHECK-NEXT: #pragma clang module import mymodule +// CHECK-NEXT: #endif // CHECK-NEXT: #pragma include_alias(<string>, "mystring.h") diff --git a/clang/test/Lexer/minimize_source_to_dependency_directives_utf8bom.c b/clang/test/Lexer/minimize_source_to_dependency_directives_utf8bom.c index d7c7d04fd1785..4659db0617472 100644 --- a/clang/test/Lexer/minimize_source_to_dependency_directives_utf8bom.c +++ b/clang/test/Lexer/minimize_source_to_dependency_directives_utf8bom.c @@ -1,6 +1,8 @@ // Test UTF8 BOM at start of file // RUN: cat %S/Inputs/bom-directives.c | od -t x1 | grep -iq 'ef[[:space:]]*bb[[:space:]]*bf' -// RUN: %clang_cc1 -DTEST -print-dependency-directives-minimized-source %S/Inputs/bom-directives.c 2>&1 | FileCheck %s +// RUN: %clang_cc1 -DTEST -print-dependency-directives-minimized-source %S/Inputs/bom-directives.c > %t 2>&1 +// RUN: FileCheck %s -input-file %t +// RUN: %clang_cc1 -Eonly %t.min.c // CHECK: #ifdef TEST // CHECK-NEXT: #include <string> diff --git a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp index 91bda85a43f57..fa3b5c31df0d0 100644 --- a/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp +++ b/clang/unittests/Lex/DependencyDirectivesScannerTest.cpp @@ -181,6 +181,10 @@ TEST(MinimizeSourceToDependencyDirectivesTest, DefineMacroArguments) { ASSERT_FALSE(minimizeSourceToDependencyDirectives("#define MACRO()", Out)); EXPECT_STREQ("#define MACRO()\n", Out.data()); + ASSERT_FALSE(minimizeSourceToDependencyDirectives( + "#define MACRO(a, b) ((a) + (b ))", Out)); + EXPECT_STREQ("#define MACRO(a,b)((a)+(b))\n", Out.data()); + ASSERT_FALSE( minimizeSourceToDependencyDirectives("#define MACRO(a, b...)", Out)); EXPECT_STREQ("#define MACRO(a,b...)\n", Out.data()); @@ -212,6 +216,21 @@ TEST(MinimizeSourceToDependencyDirectivesTest, DefineInvalidMacroArguments) { EXPECT_STREQ("#define MACRO(a*b)\n", Out.data()); } +TEST(MinimizeSourceToDependencyDirectivesTest, + ObjectLikeMacroBodyStartsWithParen) { + SmallVector<char, 128> Out; + ASSERT_FALSE(minimizeSourceToDependencyDirectives( + "#define NULL_PTR ((void *)0)\n", Out)); + EXPECT_STREQ("#define NULL_PTR ((void*)0)\n", Out.data()); + + ASSERT_FALSE(minimizeSourceToDependencyDirectives( + "#define FEATURE_ENABLED (!A || (B && !C))\n", Out)); + EXPECT_STREQ("#define FEATURE_ENABLED (!A||(B&&!C))\n", Out.data()); + ASSERT_FALSE(minimizeSourceToDependencyDirectives( + "#define WITH_COMMENT /*x*/ (-1)\n", Out)); + EXPECT_STREQ("#define WITH_COMMENT (-1)\n", Out.data()); +} + TEST(MinimizeSourceToDependencyDirectivesTest, DefineHorizontalWhitespace) { SmallVector<char, 128> Out; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
