[llvm] [CodeExtractor][Verifier] Fix OoB read when a DIExpression is used multiple times (PR #226857)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 27 17:23:46 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir
Author: Oliver Hunt (ojhunt)
<details>
<summary>Changes</summary>
#<!-- -->224360 made fixupDebugInfoPostExtraction reuse the existing DIExpression, but this is not sound if the expression is referenced multiple times, as occurs with cold/hot code splitting.
This PR is the trivial fix of restricting this change to only apply if there is a single user of the expression.
I've also added an additional verifier guard to capture these failures.
Fixes #<!-- -->226848
AI usage: Claude used to find a way to construct dbg-value-arg-index-out-of-range.ll so that I could add a verifier guard that bypassed the other existing verifier guards.
---
Full diff: https://github.com/llvm/llvm-project/pull/226857.diff
6 Files Affected:
- (modified) llvm/lib/IR/Verifier.cpp (+13)
- (modified) llvm/lib/Transforms/Utils/CodeExtractor.cpp (+6-3)
- (added) llvm/test/Assembler/invalid-diexpression-arg-negative.ll (+4)
- (added) llvm/test/DebugInfo/AArch64/dbg-value-arg-index-out-of-range.ll (+27)
- (added) llvm/test/Transforms/HotColdSplit/split-out-dbg-val-arglist.ll (+85)
- (added) llvm/test/Verifier/dbg-record-expression-arg-out-of-range.ll (+91)
``````````diff
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 2de006bfc032d..fe0cb5f833a65 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -7551,6 +7551,19 @@ void Verifier::visit(DbgVariableRecord &DVR) {
F);
visitMDNode(*DVR.getExpression(), AreDebugLocsAllowed::No);
+ const DIExpression *Expr = DVR.getExpression();
+ if (Expr->isValid() && !DVR.isKillLocation() &&
+ (isa<ValueAsMetadata>(MD) || isa<DIArgList>(MD))) {
+ unsigned NumLocationOps = DVR.getNumVariableLocationOps();
+ for (DIExpression::ExprOperand Op : Expr->expr_ops()) {
+ if (Op.getOp() != dwarf::DW_OP_LLVM_arg)
+ continue;
+ CheckDI(Op.getArg(0) < NumLocationOps,
+ "#dbg record expression references nonexistent location operand",
+ &DVR, Expr, BB, F);
+ }
+ }
+
if (DVR.isDbgAssign()) {
CheckDI(isa_and_nonnull<DIAssignID>(DVR.getRawAssignID()),
"invalid #dbg_assign DIAssignID", &DVR, DVR.getRawAssignID(), BB,
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index c8264f7c6bd2d..c18cca1b29583 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -1330,9 +1330,12 @@ static void fixupDebugInfoPostExtraction(Function &OldFunc, Function &NewFunc,
// Iterate the debug users of the Input values. If they are in the extracted
// function then update their location with the new value. If they are in
// the parent function then create a similar debug record.
- for (auto *DVR : DPUsers)
- UpdateOrInsertDebugRecord(DVR, Input, NewVal, DVR->getExpression(),
- DVR->isDbgDeclare());
+ for (auto *DVR : DPUsers) {
+ DIExpression *Expr = DVR->getNumVariableLocationOps() == 1
+ ? DVR->getExpression()
+ : DIB.createExpression();
+ UpdateOrInsertDebugRecord(DVR, Input, NewVal, Expr, DVR->isDbgDeclare());
+ }
}
auto IsInvalidLocation = [&NewFunc](Value *Location) {
diff --git a/llvm/test/Assembler/invalid-diexpression-arg-negative.ll b/llvm/test/Assembler/invalid-diexpression-arg-negative.ll
new file mode 100644
index 0000000000000..7eee384227968
--- /dev/null
+++ b/llvm/test/Assembler/invalid-diexpression-arg-negative.ll
@@ -0,0 +1,4 @@
+; RUN: not llvm-as < %s 2>&1 | FileCheck %s
+
+; CHECK: <stdin>:[[@LINE+1]]:36: error: expected unsigned integer
+!0 = !DIExpression(DW_OP_LLVM_arg, -1)
diff --git a/llvm/test/DebugInfo/AArch64/dbg-value-arg-index-out-of-range.ll b/llvm/test/DebugInfo/AArch64/dbg-value-arg-index-out-of-range.ll
new file mode 100644
index 0000000000000..f692e60a0bbf7
--- /dev/null
+++ b/llvm/test/DebugInfo/AArch64/dbg-value-arg-index-out-of-range.ll
@@ -0,0 +1,27 @@
+; RUN: llc -mtriple=arm64-apple-macosx11.0.0 -O2 -filetype=obj -o /dev/null < %s 2>&1 | FileCheck %s
+
+; CHECK: #dbg record expression references nonexistent location operand
+; CHECK: warning: ignoring invalid debug info
+
+declare void @sink(i32)
+
+define void @f(i32 %x, i32 %y) !dbg !4 {
+entry:
+ #dbg_value(i32 %x, !7, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !9)
+ %s = add i32 %x, %y, !dbg !9
+ call void @sink(i32 %s), !dbg !9
+ ret void, !dbg !9
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "t.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!5 = !DISubroutineType(types: !2)
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!7 = !DILocalVariable(name: "v", scope: !4, file: !1, line: 1, type: !6)
+!9 = !DILocation(line: 1, column: 1, scope: !4)
diff --git a/llvm/test/Transforms/HotColdSplit/split-out-dbg-val-arglist.ll b/llvm/test/Transforms/HotColdSplit/split-out-dbg-val-arglist.ll
new file mode 100644
index 0000000000000..7aaccd512871c
--- /dev/null
+++ b/llvm/test/Transforms/HotColdSplit/split-out-dbg-val-arglist.ll
@@ -0,0 +1,85 @@
+; RUN: opt -passes=hotcoldsplit -hotcoldsplit-threshold=-1 -S < %s | FileCheck %s
+
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "arm64-apple-macosx11.0.0"
+
+declare void @sink(i32) cold
+declare void @use(i32)
+
+define void @all_inputs(i32 %a, i32 %b, i1 %c) !dbg !6 {
+entry:
+ %x = add i32 %a, 1, !dbg !11
+ %y = add i32 %b, 2, !dbg !11
+ #dbg_value(!DIArgList(i32 %x, i32 %y), !9, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !11)
+ br i1 %c, label %cold, label %exit, !dbg !11
+
+cold:
+ %s = add i32 %x, %y, !dbg !11
+ call void @sink(i32 %s), !dbg !11
+ br label %exit, !dbg !11
+
+exit:
+ ret void, !dbg !11
+}
+
+define void @some_inputs(i32 %a, i32 %b, i1 %c) !dbg !12 {
+entry:
+ %x = add i32 %a, 1, !dbg !14
+ %y = add i32 %b, 2, !dbg !14
+ #dbg_value(!DIArgList(i32 %x, i32 %y), !13, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !14)
+ br i1 %c, label %cold, label %exit, !dbg !14
+
+cold:
+ %m = mul i32 %x, 3, !dbg !14
+ call void @sink(i32 %m), !dbg !14
+ br label %exit, !dbg !14
+
+exit:
+ call void @use(i32 %y), !dbg !14
+ ret void, !dbg !14
+}
+
+define void @single_location(i32 %a, i1 %c) !dbg !15 {
+entry:
+ %x = add i32 %a, 1, !dbg !17
+ #dbg_value(i32 %x, !16, !DIExpression(DW_OP_plus_uconst, 4, DW_OP_stack_value), !17)
+ br i1 %c, label %cold, label %exit, !dbg !17
+
+cold:
+ %m = mul i32 %x, 3, !dbg !17
+ call void @sink(i32 %m), !dbg !17
+ br label %exit, !dbg !17
+
+exit:
+ ret void, !dbg !17
+}
+
+; CHECK-LABEL: define internal void @all_inputs.cold.1(i32 %x, i32 %y)
+; CHECK-NOT: DW_OP_LLVM_arg
+; CHECK: ret void
+
+; CHECK-LABEL: define internal void @some_inputs.cold.1(i32 %x)
+; CHECK-NOT: DW_OP_LLVM_arg
+; CHECK: ret void
+
+; CHECK-LABEL: define internal void @single_location.cold.1(i32 %x)
+; CHECK: #dbg_value(i32 %x, ![[#]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_stack_value)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "t.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!6 = distinct !DISubprogram(name: "all_inputs", scope: !1, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!7 = !DISubroutineType(types: !2)
+!9 = !DILocalVariable(name: "v", scope: !6, file: !1, line: 2, type: !10)
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!11 = !DILocation(line: 2, column: 1, scope: !6)
+!12 = distinct !DISubprogram(name: "some_inputs", scope: !1, file: !1, line: 5, type: !7, scopeLine: 5, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!13 = !DILocalVariable(name: "w", scope: !12, file: !1, line: 6, type: !10)
+!14 = !DILocation(line: 6, column: 1, scope: !12)
+!15 = distinct !DISubprogram(name: "single_location", scope: !1, file: !1, line: 9, type: !7, scopeLine: 9, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!16 = !DILocalVariable(name: "u", scope: !15, file: !1, line: 10, type: !10)
+!17 = !DILocation(line: 10, column: 1, scope: !15)
diff --git a/llvm/test/Verifier/dbg-record-expression-arg-out-of-range.ll b/llvm/test/Verifier/dbg-record-expression-arg-out-of-range.ll
new file mode 100644
index 0000000000000..32b999d5555b2
--- /dev/null
+++ b/llvm/test/Verifier/dbg-record-expression-arg-out-of-range.ll
@@ -0,0 +1,91 @@
+; RUN: llvm-as -disable-output < %s 2>&1 | FileCheck %s
+
+; CHECK: #dbg record expression references nonexistent location operand
+; CHECK-NEXT: #dbg_value(i32 %a, ![[#]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value)
+; CHECK: #dbg record expression references nonexistent location operand
+; CHECK-NEXT: #dbg_value(!DIArgList(i32 %a, i32 %b), ![[#]], !DIExpression(DW_OP_LLVM_arg, 2, DW_OP_stack_value)
+; CHECK: #dbg record expression references nonexistent location operand
+; CHECK-NEXT: #dbg_value(i32 %a, ![[#]], !DIExpression(DW_OP_LLVM_arg, 18446744073709551615, DW_OP_stack_value)
+; CHECK: #dbg record expression references nonexistent location operand
+; CHECK-NEXT: #dbg_value(i32 %a, ![[#]], !DIExpression(DW_OP_LLVM_arg, 4294967296, DW_OP_stack_value)
+; CHECK: invalid expression
+; CHECK-NEXT: !DIExpression(4101)
+; CHECK: invalid expression
+; CHECK-NEXT: !DIExpression(4101, 0, 1, 159)
+; CHECK-NOT: #dbg record expression references nonexistent location operand
+; CHECK-NOT: invalid expression
+; CHECK: warning: ignoring invalid debug info
+
+define void @single_location(i32 %a) !dbg !4 {
+entry:
+ #dbg_value(i32 %a, !7, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !9)
+ ret void, !dbg !9
+}
+
+define void @arglist(i32 %a, i32 %b) !dbg !10 {
+entry:
+ #dbg_value(!DIArgList(i32 %a, i32 %b), !11, !DIExpression(DW_OP_LLVM_arg, 2, DW_OP_stack_value), !12)
+ #dbg_value(!DIArgList(i32 %a, i32 %b), !11, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !12)
+ ret void, !dbg !12
+}
+
+define void @wrapped_negative_index(i32 %a) !dbg !16 {
+entry:
+ #dbg_value(i32 %a, !17, !DIExpression(DW_OP_LLVM_arg, 18446744073709551615, DW_OP_stack_value), !18)
+ ret void, !dbg !18
+}
+
+define void @index_truncating_to_zero(i32 %a) !dbg !19 {
+entry:
+ #dbg_value(i32 %a, !20, !DIExpression(DW_OP_LLVM_arg, 4294967296, DW_OP_stack_value), !21)
+ ret void, !dbg !21
+}
+
+define void @missing_index(i32 %a) !dbg !22 {
+entry:
+ #dbg_value(i32 %a, !23, !DIExpression(DW_OP_LLVM_arg), !24)
+ ret void, !dbg !24
+}
+
+define void @extra_index(i32 %a) !dbg !25 {
+entry:
+ #dbg_value(i32 %a, !26, !DIExpression(DW_OP_LLVM_arg, 0, 1, DW_OP_stack_value), !27)
+ ret void, !dbg !27
+}
+
+define void @kill_location() !dbg !13 {
+entry:
+ #dbg_value(i32 poison, !14, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !15)
+ ret void, !dbg !15
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "t.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "single_location", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !DISubroutineType(types: !2)
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!7 = !DILocalVariable(name: "v", scope: !4, file: !1, line: 1, type: !6)
+!9 = !DILocation(line: 1, column: 1, scope: !4)
+!10 = distinct !DISubprogram(name: "arglist", scope: !1, file: !1, line: 2, type: !5, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!11 = !DILocalVariable(name: "w", scope: !10, file: !1, line: 2, type: !6)
+!12 = !DILocation(line: 2, column: 1, scope: !10)
+!13 = distinct !DISubprogram(name: "kill_location", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)
+!14 = !DILocalVariable(name: "u", scope: !13, file: !1, line: 3, type: !6)
+!15 = !DILocation(line: 3, column: 1, scope: !13)
+!16 = distinct !DISubprogram(name: "wrapped_negative_index", scope: !1, file: !1, line: 4, type: !5, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0)
+!17 = !DILocalVariable(name: "n", scope: !16, file: !1, line: 4, type: !6)
+!18 = !DILocation(line: 4, column: 1, scope: !16)
+!19 = distinct !DISubprogram(name: "index_truncating_to_zero", scope: !1, file: !1, line: 5, type: !5, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!20 = !DILocalVariable(name: "t", scope: !19, file: !1, line: 5, type: !6)
+!21 = !DILocation(line: 5, column: 1, scope: !19)
+!22 = distinct !DISubprogram(name: "missing_index", scope: !1, file: !1, line: 6, type: !5, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!23 = !DILocalVariable(name: "m", scope: !22, file: !1, line: 6, type: !6)
+!24 = !DILocation(line: 6, column: 1, scope: !22)
+!25 = distinct !DISubprogram(name: "extra_index", scope: !1, file: !1, line: 7, type: !5, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!26 = !DILocalVariable(name: "e", scope: !25, file: !1, line: 7, type: !6)
+!27 = !DILocation(line: 7, column: 1, scope: !25)
``````````
</details>
https://github.com/llvm/llvm-project/pull/226857
More information about the llvm-commits
mailing list