[clang] [clang-format] Avoid crash on semicolons and unmatched braces (PR #234527)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 22 02:28:48 PDT 2026


https://github.com/r4nt updated https://github.com/llvm/llvm-project/pull/234527

>From 348466213df29c0862cc7001984854ee3447ba10 Mon Sep 17 00:00:00 2001
From: Manuel Klimek <klimek at google.com>
Date: Wed, 21 Oct 2026 21:47:55 +0200
Subject: [PATCH] [clang-format] Avoid crash on semicolons and unmatched braces
 in TextProto

In UnwrappedLineParser::parseFile(), TextProto and Json inputs are parsed by
calling parseBracedList() directly instead of parseLevel(). Previously,
parseBracedList() returned early before eof() in two cases:
1. Encountering a semicolon (even though TextFormat protobufs allow ';' as an
   optional field separator), which returned false without consuming the token
   and unwound all enclosing parseBracedList() calls.
2. Encountering an unmatched closing '}' (e.g. in partial R"pb(...)pb" raw
   string fragments concatenated via StrCat), which returned after consuming
   '}' and left any remaining tokens unconsumed.

Both cases triggered the assert(eof()) in UnwrappedLineParser::parse().
---
 clang/lib/Format/UnwrappedLineParser.cpp      |  8 +++++---
 .../unittests/Format/FormatTestTextProto.cpp  | 19 +++++++++++++++++++
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 1b00e9015ea6e0..e5603feb379772 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -277,7 +277,8 @@ void UnwrappedLineParser::parseFile() {
   ScopedDeclarationState DeclarationState(*Line, DeclarationScopeStack,
                                           MustBeDeclaration);
   if (Style.isTextProto() || (Style.isJson() && FormatTok->IsFirst))
-    parseBracedList();
+    while (!eof())
+      parseBracedList();
   else
     parseLevel();
   // Make sure to format the remaining tokens.
@@ -2670,10 +2671,11 @@ bool UnwrappedLineParser::parseBracedList(bool IsAngleBracket, bool IsEnum) {
       break;
     case tok::semi:
       // JavaScript (or more precisely TypeScript) can have semicolons in braced
-      // lists (in so-called TypeMemberLists). Thus, the semicolon cannot be
+      // lists (in so-called TypeMemberLists). TextProto and Proto options also
+      // allow semicolons as field separators. Thus, the semicolon cannot be
       // used for error recovery if we have otherwise determined that this is
       // a braced list.
-      if (Style.isJavaScript()) {
+      if (Style.isJavaScript() || Style.isProto() || Style.isJson()) {
         nextToken();
         break;
       }
diff --git a/clang/unittests/Format/FormatTestTextProto.cpp b/clang/unittests/Format/FormatTestTextProto.cpp
index 6cddb838a0abe3..f919af5b78dd8f 100644
--- a/clang/unittests/Format/FormatTestTextProto.cpp
+++ b/clang/unittests/Format/FormatTestTextProto.cpp
@@ -721,6 +721,25 @@ TEST_F(FormatTestTextProto, KeepsAmpersandsNextToKeys) {
                "}");
 }
 
+TEST_F(FormatTestTextProto, SupportsSemicolonSeparators) {
+  verifyFormat("field_a: OK; field_b: OK");
+  verifyFormat("field_a: OK;\n"
+               "field_b { field_c: OK };\n"
+               "field_d: OKOKOK;\n"
+               "field_e: OK");
+  verifyFormat("msg_field <\n"
+               "  field_a: OK;\n"
+               "  field_b: <>;\n"
+               "  field_c: OK\n"
+               ">");
+}
+
+TEST_F(FormatTestTextProto, DoesNotCrashOnUnmatchedClosingBrace) {
+  verifyNoCrash("field_a: 1 } field_b: 2");
+  verifyNoCrash("} field_a: 1");
+  verifyNoCrash("msg { field_a: 1 } } field_b: 2");
+}
+
 } // namespace
 } // namespace test
 } // namespace format



More information about the cfe-commits mailing list