[llvm] 8fe3fdd - [CodeExtractor][Verifier] Fix OoB read when a DIExpression is used multiple times (#226857)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 00:51:53 PDT 2026


Author: Oliver Hunt
Date: 2026-09-28T00:51:46-07:00
New Revision: 8fe3fdd6630e04b48dd8253894a8d4ba429e4663

URL: https://github.com/llvm/llvm-project/commit/8fe3fdd6630e04b48dd8253894a8d4ba429e4663
DIFF: https://github.com/llvm/llvm-project/commit/8fe3fdd6630e04b48dd8253894a8d4ba429e4663.diff

LOG: [CodeExtractor][Verifier] Fix OoB read when a DIExpression is used multiple times (#226857)

#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.

Added: 
    llvm/test/Assembler/invalid-diexpression-arg-negative.ll
    llvm/test/DebugInfo/AArch64/dbg-value-arg-index-out-of-range.ll
    llvm/test/Transforms/HotColdSplit/split-out-dbg-val-arglist.ll
    llvm/test/Verifier/dbg-record-expression-arg-out-of-range.ll

Modified: 
    llvm/lib/IR/Verifier.cpp
    llvm/lib/Transforms/Utils/CodeExtractor.cpp

Removed: 
    


################################################################################
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)


        


More information about the llvm-commits mailing list