[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