[clang] [clang-format] Fix hang when object-like macro is called with arguments (PR #216472)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Aug 15 02:06:50 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-format
Author: Aditya Goyal (goyaladitya05)
<details>
<summary>Changes</summary>
### Cause
When a macro configured as object-like (e.g. `Macros: [CASE=case]`) is called with arguments (`CASE(1, "1")`), the parser rewinds the token stream to reparse the arguments as regular tokens. It then called `nextToken()`, which first pushes the current `FormatTok` onto the line being built, but at that point `FormatTok` is the token *past* the macro call's closing paren, left over from `parseMacroCall()`. The rewound stream serves that token again later, so it ends up in the unwrapped lines twice.
A duplicated token makes `AnnotatedLine`'s Next/Previous re-linking cyclic, and the first backward walk over the line (`TokenAnnotator::calculateUnbreakableTailLengths`) loops forever. With a single macro call the two copies land in different lines, which terminates but silently corrupts the layout; with two calls (or one top-level call) they land in the same line and clang-format hangs.
### Changes
This PR restores `FormatTok` with `Tokens->getNextToken()` instead, which yields the identical stream state without the bogus push, matching the direct restores in the neighboring fallback paths.
The expected output of `KeepParensWhenExpandingObjectLikeMacros` changed because it exercised the silent variant of the bug: the phantom `;` pushed onto the `void f() {` line is what kept the macro call from being merged with the function header.
Fixes #<!-- -->163338.
---
Full diff: https://github.com/llvm/llvm-project/pull/216472.diff
2 Files Affected:
- (modified) clang/lib/Format/UnwrappedLineParser.cpp (+2-1)
- (modified) clang/unittests/Format/FormatTestMacroExpansion.cpp (+16-2)
``````````diff
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 8e6f7e2f2ce07..4520e4cb52407 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -5110,7 +5110,8 @@ void UnwrappedLineParser::readToken(int LevelDifference) {
Args.reset();
UnexpandedLine->Tokens.resize(1);
Tokens->setPosition(Position);
- nextToken();
+ // Not nextToken(), which would push the stale FormatTok onto the line.
+ FormatTok = Tokens->getNextToken();
assert(!Args && Macros.objectLike(ID->TokenText));
}
if ((!Args && Macros.objectLike(ID->TokenText)) ||
diff --git a/clang/unittests/Format/FormatTestMacroExpansion.cpp b/clang/unittests/Format/FormatTestMacroExpansion.cpp
index d391fe3d715c3..8ff3282d31aee 100644
--- a/clang/unittests/Format/FormatTestMacroExpansion.cpp
+++ b/clang/unittests/Format/FormatTestMacroExpansion.cpp
@@ -231,8 +231,7 @@ a))",
TEST_F(FormatTestMacroExpansion, KeepParensWhenExpandingObjectLikeMacros) {
FormatStyle Style = getLLVMStyle();
Style.Macros.push_back("FN=class C { int f");
- verifyFormat("void f() {\n"
- " FN(a *b);\n"
+ verifyFormat("void f() { FN(a *b);\n"
" };\n"
"}",
Style);
@@ -301,6 +300,21 @@ TEST_F(FormatTestMacroExpansion, IndentChildrenWithinMacroCall) {
Style);
}
+TEST_F(FormatTestMacroExpansion, ObjectLikeMacroCalledWithArgsDoesNotHang) {
+ FormatStyle Style = getLLVMStyle();
+ Style.Macros.push_back("CASE=case");
+ verifyNoCrash("const char *fct(int wki) {\n"
+ " switch (wki) {\n"
+ " CASE(1, \"1\");\n"
+ " CASE(2, \"2\");\n"
+ " default:\n"
+ " return \"123\";\n"
+ " }\n"
+ "}",
+ Style);
+ verifyNoCrash("CASE(1, \"1\");", Style);
+}
+
} // namespace
} // namespace test
} // namespace format
``````````
</details>
https://github.com/llvm/llvm-project/pull/216472
More information about the cfe-commits
mailing list