[clang] [clang][Sema] Diagnose nested assembler dialect alternatives in inline `asm` strings (PR #225306)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 21 23:23:45 PDT 2026


https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/225306

Fixes #204773

The asm template in the report wraps the whole instruction sequence in `{...}` and then uses `{l}` inside it. GCC's dialect-alternative syntax `{att|intel}` does not nest, and GCC rejects this with a plain error. Clang never looked at the brace structure: `AnalyzeAsmString` rewrote every `{`, `|` and `}` into the backend markers `$(`, `$|` and `$)` and passed the result on. The X86 AsmPrinter is the first thing that notices the nesting, and at that stage its only option is `report_fatal_error`, which the driver then reports as a crash. An opened `{` that is never closed was worse, since nothing caught it at all and the backend silently emitted whichever text happened to fall in variant 0.

`AnalyzeAsmString` now remembers where the current alternative was opened. A second `{` before the closing `}` is reported as nested dialect alternatives, and reaching the end of the string inside an alternative is reported as unterminated, both pointing at the offending brace in the source string. The check only applies on targets where braces carry dialect meaning; targets that set `NoAsmVariants` keep treating them as ordinary characters, and the `%{` / `%}` escapes are unaffected. 

>From 055bce5fd8fe4a0a22b0f3f546be7ba9d003cce8 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 22 Sep 2026 11:51:38 +0530
Subject: [PATCH] [clang][Sema] Diagnose nested assembler dialect alternatives
 in inline asm strings

GCCAsmStmt::AnalyzeAsmString rewrote {, | and } into the backend's
$(, $| and $) markers without checking their structure, so a nested
{a{b}} alternative reached the AsmPrinter and was reported with
report_fatal_error. Track the open alternative while scanning and
report nested or unterminated alternatives as regular Sema errors at
their location in the string, matching GCC, which rejects both.

Fixes #204773
---
 clang/docs/ReleaseNotes.md                    |  4 +++
 .../include/clang/Basic/DiagnosticASTKinds.td |  4 +++
 clang/lib/AST/Stmt.cpp                        | 29 +++++++++++++++++--
 clang/test/Sema/asm.c                         | 13 +++++++++
 clang/test/Sema/inline-asm-validate-aarch64.c |  4 +++
 5 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f4a34a37aff52..61dacc99a9f2b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -451,6 +451,10 @@ features cannot lower the translation-unit ABI level;
 - Clang now rejects inline asm constraints and clobbers that contain an
   embedded null character, instead of silently truncating them. (#GH173900)
 
+- Clang now rejects nested or unterminated assembler dialect alternatives
+  (`{att|intel}`) in an inline asm string, instead of producing a backend fatal
+  error or silently emitting the wrong dialect. (#GH204773)
+
 - Added `-Wstringop-overread` to warn when `memcpy`, `memmove`, `memcmp`,
   and related builtins read more bytes than the source buffer size (#GH83728).
 
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 0aca1f75428f8..ac8e7ed7c2f6d 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -461,6 +461,10 @@ let CategoryName = "Inline Assembly Issue" in {
     "empty symbolic operand name in inline assembly string">;
   def err_asm_invalid_operand_number : Error<
     "invalid operand number in inline asm string">;
+  def err_asm_nested_dialect_alternatives : Error<
+    "nested assembler dialect alternatives in inline assembly string">;
+  def err_asm_unterminated_dialect_alternative : Error<
+    "unterminated assembler dialect alternative in inline assembly string">;
 }
 
 // vtable related.
diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index 15d0e6435aaf3..4513e8d6f665f 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -699,12 +699,19 @@ unsigned GCCAsmStmt::AnalyzeAsmString(SmallVectorImpl<AsmStringPiece>&Pieces,
 
   bool HasVariants = !C.getTargetInfo().hasNoAsmVariants();
 
+  // Offset of the '{' opening the current {a|b|c} dialect alternative, if any.
+  std::optional<unsigned> VariantStartOffs;
+
   unsigned LastAsmStringToken = 0;
   unsigned LastAsmStringOffset = 0;
 
   while (true) {
     // Done with the string?
     if (CurPtr == StrEnd) {
+      if (VariantStartOffs) {
+        DiagOffs = *VariantStartOffs;
+        return diag::err_asm_unterminated_dialect_alternative;
+      }
       if (!CurStringPiece.empty())
         Pieces.push_back(AsmStringPiece(CurStringPiece));
       return 0;
@@ -713,9 +720,27 @@ unsigned GCCAsmStmt::AnalyzeAsmString(SmallVectorImpl<AsmStringPiece>&Pieces,
     char CurChar = *CurPtr++;
     switch (CurChar) {
     case '$': CurStringPiece += "$$"; continue;
-    case '{': CurStringPiece += (HasVariants ? "$(" : "{"); continue;
+    case '{':
+      if (!HasVariants) {
+        CurStringPiece += '{';
+        continue;
+      }
+      if (VariantStartOffs) {
+        DiagOffs = CurPtr - StrStart - 1;
+        return diag::err_asm_nested_dialect_alternatives;
+      }
+      VariantStartOffs = CurPtr - StrStart - 1;
+      CurStringPiece += "$(";
+      continue;
     case '|': CurStringPiece += (HasVariants ? "$|" : "|"); continue;
-    case '}': CurStringPiece += (HasVariants ? "$)" : "}"); continue;
+    case '}':
+      if (!HasVariants) {
+        CurStringPiece += '}';
+        continue;
+      }
+      VariantStartOffs.reset();
+      CurStringPiece += "$)";
+      continue;
     case '%':
       break;
     default:
diff --git a/clang/test/Sema/asm.c b/clang/test/Sema/asm.c
index cc9acac1e169d..4434adfeee5e7 100644
--- a/clang/test/Sema/asm.c
+++ b/clang/test/Sema/asm.c
@@ -404,3 +404,16 @@ void test20(char x) {
   asm ("fabs" : "=t" (d): "0" (v)); // expected-error {{unsupported inline asm: input with type 'int2' (vector of 2 'int' values) matching output with type 'double'}}
   asm ("fabs" : "=t" (v): "0" (d)); // expected-error {{unsupported inline asm: input with type 'double' matching output with type 'int2' (vector of 2 'int' values)}}
 }
+
+// GH204773
+void test21(int x) {
+  asm ("{cmpl{l}\t$d,%c0;je\t1f;addw{l}\t$d,%c0;jmp\t2f;1:decl\t%c0;2:}" : : "g"(x)); // expected-error {{nested assembler dialect alternatives in inline assembly string}}
+  asm ("{addl %0, %0|add %0, %0}" : : "r"(x)); // ok
+  asm ("{a|b}{c|d}" : : "r"(x)); // ok
+  asm ("{a|b" : : "r"(x)); // expected-error {{unterminated assembler dialect alternative in inline assembly string}}
+  asm ("{" : : "r"(x)); // expected-error {{unterminated assembler dialect alternative in inline assembly string}}
+  asm ("a}b|c" : : "r"(x)); // ok
+  asm ("%{%{%}%}" : : "r"(x)); // ok, escaped braces
+  asm ("{%{a%}|b}" : : "r"(x)); // ok
+  asm ("{{"); // ok, simple asm
+}
diff --git a/clang/test/Sema/inline-asm-validate-aarch64.c b/clang/test/Sema/inline-asm-validate-aarch64.c
index 1e753d40d8ca0..da4e59859743d 100644
--- a/clang/test/Sema/inline-asm-validate-aarch64.c
+++ b/clang/test/Sema/inline-asm-validate-aarch64.c
@@ -10,6 +10,10 @@ void test_s(int i) {
   /// Codegen error
   asm("" :: "S"(i));
   asm("" :: "S"(test_s(i))); // expected-error{{invalid type 'void' in asm input for constraint 'S'}}
+
+  // GH204773: braces are ordinary characters on this target.
+  asm("{a{b}}" :: "r"(i));
+  asm("{" :: "r"(i));
 }
 #else
 uint8_t constraint_r(uint8_t *addr) {



More information about the cfe-commits mailing list