[llvm] [Dwarf] Don't crash in dwarf::isC() on vendor-defined languages (PR #223739)

Matt Davis via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 12:58:35 PDT 2026


https://github.com/enferex updated https://github.com/llvm/llvm-project/pull/223739

>From 3cba5954f7d1e71db2fc50c27f4a5a32989dac1d Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Sat, 29 Aug 2026 12:47:07 +0000
Subject: [PATCH 1/6] Don't crash in dwarf::isC() on vendor-defined languages

isC() enumerates every known SourceLanguage and falls off the end of the
switch into llvm_unreachable("Unknown language kind."). DW_LANG_lo_user
and DW_LANG_hi_user are both explicit cases, but every vendor-defined
code strictly between them is not, so any of them aborts an
asserts-enabled build.

Assisted-by: LLM
---
 llvm/include/llvm/BinaryFormat/Dwarf.h        |  5 ++++
 .../DebugInfo/X86/vendor-source-language.ll   | 26 +++++++++++++++++++
 2 files changed, 31 insertions(+)
 create mode 100644 llvm/test/DebugInfo/X86/vendor-source-language.ll

diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.h b/llvm/include/llvm/BinaryFormat/Dwarf.h
index 502f42b9ddc7ea..193e4ddbd71fb5 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.h
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.h
@@ -825,6 +825,11 @@ inline bool isC(SourceLanguage S) {
   case DW_LANG_Gleam:
     return false;
   }
+  // The switch above covers the DW_LANG_{lo,hi}_user boundaries, but every
+  // vendor-defined code in between is a valid SourceLanguage that no case
+  // enumerates. None of them is C.
+  if (S > DW_LANG_lo_user && S < DW_LANG_hi_user)
+    return false;
   llvm_unreachable("Unknown language kind.");
 }
 
diff --git a/llvm/test/DebugInfo/X86/vendor-source-language.ll b/llvm/test/DebugInfo/X86/vendor-source-language.ll
new file mode 100644
index 00000000000000..bff9a363159d93
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/vendor-source-language.ll
@@ -0,0 +1,26 @@
+; DW_LANG_lo_user and DW_LANG_hi_user are enumerated by dwarf::isC(), but the
+; vendor-defined codes between them are not, and used to fall through to the
+; trailing llvm_unreachable. Emitting a prototyped subprogram in such a
+; language calls isC() and must not crash.
+
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t.o < %s
+; RUN: llvm-dwarfdump -debug-info %t.o | FileCheck %s
+
+; CHECK: DW_TAG_compile_unit
+; CHECK:   DW_AT_language (0x8003)
+
+define void @f() !dbg !4 {
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+; 32771 == DW_LANG_lo_user (0x8000) + 3, a vendor-defined source language.
+!0 = distinct !DICompileUnit(language: 32771, file: !1, emissionKind: FullDebug)
+!1 = !DIFile(filename: "a.c", directory: "/")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !{i32 2, !"Dwarf Version", i32 5}
+!4 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !5, unit: !0, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}

>From 5fcd354fef24822a9ad3cdbc70437358d3e108ab Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Fri, 18 Sep 2026 12:32:55 +0000
Subject: [PATCH 2/6] Refactor Dwarf::isC to be closer to isFortran/isCPlusPlus

---
 llvm/include/llvm/BinaryFormat/Dwarf.h | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.h b/llvm/include/llvm/BinaryFormat/Dwarf.h
index 193e4ddbd71fb5..d04bfce5dfc259 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.h
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.h
@@ -742,6 +742,7 @@ inline bool isFortran(SourceLanguage S) {
 }
 
 inline bool isC(SourceLanguage S) {
+  bool result = false;
   // Deliberately enumerate all the language options so we get a warning when
   // new language options are added (-Wswitch) that'll hopefully help keep this
   // switch up-to-date when new C++ versions are added.
@@ -753,7 +754,8 @@ inline bool isC(SourceLanguage S) {
   case DW_LANG_C99:
   case DW_LANG_C:
   case DW_LANG_ObjC:
-    return true;
+    result = true;
+    break;
   case DW_LANG_C_plus_plus:
   case DW_LANG_C_plus_plus_03:
   case DW_LANG_C_plus_plus_11:
@@ -823,14 +825,11 @@ inline bool isC(SourceLanguage S) {
   case DW_LANG_Erlang:
   case DW_LANG_Elixir:
   case DW_LANG_Gleam:
-    return false;
+    result = false;
+    break;
   }
-  // The switch above covers the DW_LANG_{lo,hi}_user boundaries, but every
-  // vendor-defined code in between is a valid SourceLanguage that no case
-  // enumerates. None of them is C.
-  if (S > DW_LANG_lo_user && S < DW_LANG_hi_user)
-    return false;
-  llvm_unreachable("Unknown language kind.");
+
+  return result;
 }
 
 inline TypeKind getArrayIndexTypeEncoding(SourceLanguage S) {

>From 78d523672b206bc6c9a07a04b15d3071801a8ef9 Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Fri, 18 Sep 2026 14:21:20 +0000
Subject: [PATCH 3/6] Return early in Dwarf::isC instead of capturing state.

---
 llvm/include/llvm/BinaryFormat/Dwarf.h | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.h b/llvm/include/llvm/BinaryFormat/Dwarf.h
index d04bfce5dfc259..fe2c73b28ad207 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.h
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.h
@@ -742,7 +742,6 @@ inline bool isFortran(SourceLanguage S) {
 }
 
 inline bool isC(SourceLanguage S) {
-  bool result = false;
   // Deliberately enumerate all the language options so we get a warning when
   // new language options are added (-Wswitch) that'll hopefully help keep this
   // switch up-to-date when new C++ versions are added.
@@ -754,8 +753,7 @@ inline bool isC(SourceLanguage S) {
   case DW_LANG_C99:
   case DW_LANG_C:
   case DW_LANG_ObjC:
-    result = true;
-    break;
+    return true;
   case DW_LANG_C_plus_plus:
   case DW_LANG_C_plus_plus_03:
   case DW_LANG_C_plus_plus_11:
@@ -825,11 +823,10 @@ inline bool isC(SourceLanguage S) {
   case DW_LANG_Erlang:
   case DW_LANG_Elixir:
   case DW_LANG_Gleam:
-    result = false;
-    break;
+    return false;
   }
 
-  return result;
+  return false;
 }
 
 inline TypeKind getArrayIndexTypeEncoding(SourceLanguage S) {

>From 706b812d96b55f8581e068fa6417016180b9ce3e Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Mon, 21 Sep 2026 11:49:54 +0000
Subject: [PATCH 4/6] Minor grammatical correction.

---
 llvm/test/DebugInfo/X86/vendor-source-language.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/DebugInfo/X86/vendor-source-language.ll b/llvm/test/DebugInfo/X86/vendor-source-language.ll
index bff9a363159d93..8671a7aabcbdda 100644
--- a/llvm/test/DebugInfo/X86/vendor-source-language.ll
+++ b/llvm/test/DebugInfo/X86/vendor-source-language.ll
@@ -1,5 +1,5 @@
 ; DW_LANG_lo_user and DW_LANG_hi_user are enumerated by dwarf::isC(), but the
-; vendor-defined codes between them are not, and used to fall through to the
+; vendor-defined codes between them are not, and used to fall through to a
 ; trailing llvm_unreachable. Emitting a prototyped subprogram in such a
 ; language calls isC() and must not crash.
 

>From 8a75c62d5e4eb5a79cfe555799d2cc1d105dd34e Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Mon, 21 Sep 2026 12:17:14 +0000
Subject: [PATCH 5/6] Cover non-vendor unrecognised languages in the isC() test

Rename the test and parameterise it over unnamed/undefined language
codes, including a vendor code.

Also test that DIFlagPrototyped does not become DW_AT_prototyped, so the
test checks that an unrecognised language is not C rather than merely
that llc survived.

Assisted-by: LLM

Co-Authored-By: Claude Opus 5 (1M context) <noreply at anthropic.com>
---
 .../DebugInfo/X86/unknown-source-language.ll  | 52 +++++++++++++++++++
 .../DebugInfo/X86/vendor-source-language.ll   | 26 ----------
 2 files changed, 52 insertions(+), 26 deletions(-)
 create mode 100644 llvm/test/DebugInfo/X86/unknown-source-language.ll
 delete mode 100644 llvm/test/DebugInfo/X86/vendor-source-language.ll

diff --git a/llvm/test/DebugInfo/X86/unknown-source-language.ll b/llvm/test/DebugInfo/X86/unknown-source-language.ll
new file mode 100644
index 00000000000000..b9070f8b49bb86
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/unknown-source-language.ll
@@ -0,0 +1,52 @@
+; dwarf::isC() enumerates every named DW_LANG_* code, but a DICompileUnit may
+; name any source languages including those not defined in Dwarf.def: gaps in
+; the standard range and vendor codes strictly between DW_LANG_lo_user and
+; DW_LANG_hi_user. Emitting a prototyped subprogram queries isC(), which used
+; to reach a trailing llvm_unreachable for unknown language codes (including
+; the vendor codes). Check that each kind is emitted unrecognised rather than
+; crashing, and that none is treated as C.
+;
+; If a code below is ever assigned a name in Dwarf.def, this test will start
+; failing; replace it with another unassigned one.
+
+; RUN: rm -rf %t && mkdir %t
+
+; 0x0029: a gap in the standard range (Dwarf.def jumps 0x0028 -> 0x002a).
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %s
+; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x0029
+
+; 0x7fff: a standard-range code above the highest assigned one (0x0048).
+; RUN: sed -e "s/language: 41/language: 32767/" %s > %t/test.ll
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %t/test.ll
+; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x7fff
+
+; 0x8003: a vendor code strictly between DW_LANG_lo_user and DW_LANG_hi_user.
+; RUN: sed -e "s/language: 41/language: 32771/" %s > %t/test.ll
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %t/test.ll
+; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x8003
+
+; CHECK:     DW_TAG_compile_unit
+; CHECK:       DW_AT_language ([[LANG]])
+
+; An unrecognised language is not C, so DIFlagPrototyped must not become
+; DW_AT_prototyped.
+; CHECK:     DW_TAG_subprogram
+; CHECK-NOT:   DW_AT_prototyped
+; CHECK:       DW_AT_name ("f")
+; CHECK-NOT:   DW_AT_prototyped
+; CHECK:     NULL
+
+define void @f() !dbg !4 {
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: 41, file: !1, emissionKind: FullDebug)
+!1 = !DIFile(filename: "a.c", directory: "/")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !{i32 2, !"Dwarf Version", i32 5}
+!4 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !5, unit: !0, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
diff --git a/llvm/test/DebugInfo/X86/vendor-source-language.ll b/llvm/test/DebugInfo/X86/vendor-source-language.ll
deleted file mode 100644
index 8671a7aabcbdda..00000000000000
--- a/llvm/test/DebugInfo/X86/vendor-source-language.ll
+++ /dev/null
@@ -1,26 +0,0 @@
-; DW_LANG_lo_user and DW_LANG_hi_user are enumerated by dwarf::isC(), but the
-; vendor-defined codes between them are not, and used to fall through to a
-; trailing llvm_unreachable. Emitting a prototyped subprogram in such a
-; language calls isC() and must not crash.
-
-; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t.o < %s
-; RUN: llvm-dwarfdump -debug-info %t.o | FileCheck %s
-
-; CHECK: DW_TAG_compile_unit
-; CHECK:   DW_AT_language (0x8003)
-
-define void @f() !dbg !4 {
-  ret void
-}
-
-!llvm.dbg.cu = !{!0}
-!llvm.module.flags = !{!2, !3}
-
-; 32771 == DW_LANG_lo_user (0x8000) + 3, a vendor-defined source language.
-!0 = distinct !DICompileUnit(language: 32771, file: !1, emissionKind: FullDebug)
-!1 = !DIFile(filename: "a.c", directory: "/")
-!2 = !{i32 2, !"Debug Info Version", i32 3}
-!3 = !{i32 2, !"Dwarf Version", i32 5}
-!4 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !5, unit: !0, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition)
-!5 = !DISubroutineType(types: !6)
-!6 = !{null}

>From 0aa8cca811844177427234ba908f3d3ff70e95ea Mon Sep 17 00:00:00 2001
From: Matt Davis <mattd at nvidia.com>
Date: Mon, 21 Sep 2026 18:46:57 +0000
Subject: [PATCH 6/6] Drop the 0x0029 case from the isC() test

0x0029 is a gap in the standard range that Dwarf.def could plausibly fill
later, which would silently break the test. 0x7fff covers the same class --
a standard-range code no case enumerates -- without that risk, so test it
and the vendor code alone.

Assisted-by: LLM

Co-Authored-By: Claude Opus 5 (1M context) <noreply at anthropic.com>
---
 .../DebugInfo/X86/unknown-source-language.ll  | 34 ++++++++-----------
 1 file changed, 14 insertions(+), 20 deletions(-)

diff --git a/llvm/test/DebugInfo/X86/unknown-source-language.ll b/llvm/test/DebugInfo/X86/unknown-source-language.ll
index b9070f8b49bb86..aa7de1ba1caebc 100644
--- a/llvm/test/DebugInfo/X86/unknown-source-language.ll
+++ b/llvm/test/DebugInfo/X86/unknown-source-language.ll
@@ -1,27 +1,21 @@
 ; dwarf::isC() enumerates every named DW_LANG_* code, but a DICompileUnit may
-; name any source languages including those not defined in Dwarf.def: gaps in
-; the standard range and vendor codes strictly between DW_LANG_lo_user and
-; DW_LANG_hi_user. Emitting a prototyped subprogram queries isC(), which used
-; to reach a trailing llvm_unreachable for unknown language codes (including
-; the vendor codes). Check that each kind is emitted unrecognised rather than
-; crashing, and that none is treated as C.
-;
-; If a code below is ever assigned a name in Dwarf.def, this test will start
-; failing; replace it with another unassigned one.
-
-; RUN: rm -rf %t && mkdir %t
-
-; 0x0029: a gap in the standard range (Dwarf.def jumps 0x0028 -> 0x002a).
+; name any source language, including ones no case covers: standard codes not
+; yet added to Dwarf.def, and vendor codes strictly between DW_LANG_lo_user and
+; DW_LANG_hi_user (both of which *are* named). Emitting a prototyped subprogram
+; queries isC(), which used to fall through to a trailing llvm_unreachable for
+; all of them. Check that each kind is emitted unrecognised rather than
+; crashing, and that neither is treated as C.
+
+; RUN: rm -rf %t
+; RUN: mkdir %t
+
+; 0x7fff: a standard-range code above the highest assigned one (0x0048). If it
+; is ever assigned a name in Dwarf.def, replace it with another unassigned one.
 ; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %s
-; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x0029
-
-; 0x7fff: a standard-range code above the highest assigned one (0x0048).
-; RUN: sed -e "s/language: 41/language: 32767/" %s > %t/test.ll
-; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %t/test.ll
 ; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x7fff
 
 ; 0x8003: a vendor code strictly between DW_LANG_lo_user and DW_LANG_hi_user.
-; RUN: sed -e "s/language: 41/language: 32771/" %s > %t/test.ll
+; RUN: sed -e "s/language: 32767/language: 32771/" %s > %t/test.ll
 ; RUN: llc -mtriple=x86_64-unknown-linux-gnu -filetype=obj -o %t/test.o < %t/test.ll
 ; RUN: llvm-dwarfdump -debug-info %t/test.o | FileCheck %s -DLANG=0x8003
 
@@ -43,7 +37,7 @@ define void @f() !dbg !4 {
 !llvm.dbg.cu = !{!0}
 !llvm.module.flags = !{!2, !3}
 
-!0 = distinct !DICompileUnit(language: 41, file: !1, emissionKind: FullDebug)
+!0 = distinct !DICompileUnit(language: 32767, file: !1, emissionKind: FullDebug)
 !1 = !DIFile(filename: "a.c", directory: "/")
 !2 = !{i32 2, !"Debug Info Version", i32 3}
 !3 = !{i32 2, !"Dwarf Version", i32 5}



More information about the llvm-commits mailing list