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

Reply via email to