Skip to content

Commit a6e1700

Browse files
authored
[Utils][Local] Preserve !nosanitize in combineMetadata when merging instructions (#148376)
`combineMetadata` helper currently drops `!nosanitize` metadata when merging two instructions, even if both originally carried `!nosanitize`. This is problematic because `!nosanitize` is a key mechanism used by sanitizer (e.g., ASan) to suppress instrumentation. Removing it can lead to unintended sanitizer behavior. This patch adds `nosanitize` to the whitelist in combineMetadata, preserving it only if both instructions carry `!nosanitize`; otherwise, it is dropped. This patch also adds corresponding tests in a test file and regenerates it. --- ### Details **Example (see [Godbolt](https://godbolt.org/z/83P5eWczx) for details**): ```llvm %v1 = load i32, ptr %p, !nosanitize %v2 = load i32, ptr %p, !nosanitize ``` When merged via `combineMetadata(%v1, %v2, ...)`, the resulting instruction loses its `!nosanitize` metadata. Tools such as UBSan and AFL rely on `nosanitize` to prevent unwanted transformations or checks. However, the current implementation of combineMetadata mistakenly drops !nosanitize. This may lead to unintended behavior during optimization. For example, under `-fsanitize=address,undefined -O2`, IR emitted by UBSan may lose its `!nosanitize` metadata due to the incorrect metadata merging in optimization. As a result, ASan could unexpectedly instrument those instructions. > Note: due to the current UBSan handlers having relatively coarse-grained attributes, this specific case is difficult to reproduce end-to-end from source code—UBSan currently inhibits such optimizations (refer to #135135 for details). Still, I believe it's necessary to fix this now, to support future versions of UBSan that might allow such optimizations, and to support third-party tools (such as AFL-based fuzzers) that rely on the presence of !nosanitize.
1 parent cad62df commit a6e1700

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

llvm/lib/Transforms/Utils/Local.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3381,7 +3381,11 @@ static void combineMetadata(Instruction *K, const Instruction *J,
33813381
K->setMetadata(Kind,
33823382
MDNode::getMostGenericNoaliasAddrspace(JMD, KMD));
33833383
break;
3384-
}
3384+
case LLVMContext::MD_nosanitize:
3385+
// Preserve !nosanitize if both K and J have it.
3386+
K->setMetadata(Kind, JMD);
3387+
break;
3388+
}
33853389
}
33863390
// Set !invariant.group from J if J has it. If both instructions have it
33873391
// then we will just pick it from J - even when they are different.

llvm/test/Transforms/GVN/metadata.ll

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ define i32 @test8(ptr %p) {
112112
define i32 @load_noundef_load(ptr %p) {
113113
; CHECK-LABEL: define i32 @load_noundef_load
114114
; CHECK-SAME: (ptr [[P:%.*]]) {
115-
; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !range [[RNG0]], !noundef !6
115+
; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !range [[RNG0]], !noundef [[META6:![0-9]+]]
116116
; CHECK-NEXT: [[C:%.*]] = add i32 [[A]], [[A]]
117117
; CHECK-NEXT: ret i32 [[C]]
118118
;
@@ -138,7 +138,7 @@ define i32 @load_load_noundef(ptr %p) {
138138
define void @load_dereferenceable_dominating(ptr %p) {
139139
; CHECK-LABEL: define void @load_dereferenceable_dominating
140140
; CHECK-SAME: (ptr [[P:%.*]]) {
141-
; CHECK-NEXT: [[A:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable !7
141+
; CHECK-NEXT: [[A:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable [[META7:![0-9]+]]
142142
; CHECK-NEXT: call void @use.ptr(ptr [[A]])
143143
; CHECK-NEXT: call void @use.ptr(ptr [[A]])
144144
; CHECK-NEXT: ret void
@@ -185,7 +185,7 @@ define void @load_ptr_nonnull_to_i64(ptr %p) {
185185
define void @load_ptr_nonnull_noundef_to_i64(ptr %p) {
186186
; CHECK-LABEL: define void @load_ptr_nonnull_noundef_to_i64
187187
; CHECK-SAME: (ptr [[P:%.*]]) {
188-
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !nonnull !6, !noundef !6
188+
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META6]], !noundef [[META6]]
189189
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
190190
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
191191
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
@@ -202,7 +202,7 @@ define void @load_ptr_nonnull_noundef_to_i64(ptr %p) {
202202
define void @load_ptr_invariant_load_to_i64(ptr %p) {
203203
; CHECK-LABEL: define void @load_ptr_invariant_load_to_i64
204204
; CHECK-SAME: (ptr [[P:%.*]]) {
205-
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !invariant.load !6
205+
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !invariant.load [[META6]]
206206
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
207207
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
208208
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
@@ -219,7 +219,7 @@ define void @load_ptr_invariant_load_to_i64(ptr %p) {
219219
define void @load_ptr_dereferenceable_to_i64(ptr %p) {
220220
; CHECK-LABEL: define void @load_ptr_dereferenceable_to_i64
221221
; CHECK-SAME: (ptr [[P:%.*]]) {
222-
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable !7
222+
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable [[META7]]
223223
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
224224
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
225225
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
@@ -236,7 +236,7 @@ define void @load_ptr_dereferenceable_to_i64(ptr %p) {
236236
define void @load_ptr_dereferenceable_or_null_to_i64(ptr %p) {
237237
; CHECK-LABEL: define void @load_ptr_dereferenceable_or_null_to_i64
238238
; CHECK-SAME: (ptr [[P:%.*]]) {
239-
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable_or_null !7
239+
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable_or_null [[META7]]
240240
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
241241
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
242242
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
@@ -409,6 +409,82 @@ join:
409409
ret void
410410
}
411411

412+
; We should preserve the !nosanitize if both insns have it.
413+
define void @test_nosanitize1(ptr %p) {
414+
; CHECK-LABEL: define void @test_nosanitize1
415+
; CHECK-SAME: (ptr [[P:%.*]]) {
416+
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4, !nosanitize [[META6]]
417+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
418+
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
419+
; CHECK: if:
420+
; CHECK-NEXT: call void @use.i32(i32 0)
421+
; CHECK-NEXT: br label [[JOIN]]
422+
; CHECK: join:
423+
; CHECK-NEXT: ret void
424+
;
425+
%v1 = load i32, ptr %p, !nosanitize !11
426+
%cond = icmp eq i32 %v1, 0
427+
br i1 %cond, label %if, label %join
428+
429+
if:
430+
%v2 = load i32, ptr %p, !nosanitize !11
431+
call void @use.i32(i32 %v2)
432+
br label %join
433+
434+
join:
435+
ret void
436+
}
437+
438+
define void @test_nosanitize2(ptr %p) {
439+
; CHECK-LABEL: define void @test_nosanitize2
440+
; CHECK-SAME: (ptr [[P:%.*]]) {
441+
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4
442+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
443+
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
444+
; CHECK: if:
445+
; CHECK-NEXT: call void @use.i32(i32 0)
446+
; CHECK-NEXT: br label [[JOIN]]
447+
; CHECK: join:
448+
; CHECK-NEXT: ret void
449+
;
450+
%v1 = load i32, ptr %p, !nosanitize !11
451+
%cond = icmp eq i32 %v1, 0
452+
br i1 %cond, label %if, label %join
453+
454+
if:
455+
%v2 = load i32, ptr %p
456+
call void @use.i32(i32 %v2)
457+
br label %join
458+
459+
join:
460+
ret void
461+
}
462+
463+
define void @test_nosanitize3(ptr %p) {
464+
; CHECK-LABEL: define void @test_nosanitize3
465+
; CHECK-SAME: (ptr [[P:%.*]]) {
466+
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4
467+
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
468+
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
469+
; CHECK: if:
470+
; CHECK-NEXT: call void @use.i32(i32 0)
471+
; CHECK-NEXT: br label [[JOIN]]
472+
; CHECK: join:
473+
; CHECK-NEXT: ret void
474+
;
475+
%v1 = load i32, ptr %p
476+
%cond = icmp eq i32 %v1, 0
477+
br i1 %cond, label %if, label %join
478+
479+
if:
480+
%v2 = load i32, ptr %p, !nosanitize !11
481+
call void @use.i32(i32 %v2)
482+
br label %join
483+
484+
join:
485+
ret void
486+
}
487+
412488
!0 = !{i32 0, i32 2}
413489
!1 = !{i32 3, i32 5}
414490
!2 = !{i32 2, i32 5}
@@ -430,8 +506,8 @@ join:
430506
; CHECK: [[RNG3]] = !{i32 -5, i32 -2, i32 1, i32 5}
431507
; CHECK: [[RNG4]] = !{i32 10, i32 1}
432508
; CHECK: [[RNG5]] = !{i32 3, i32 4, i32 5, i32 2}
433-
; CHECK: [[META6:![0-9]+]] = !{}
434-
; CHECK: [[META7:![0-9]+]] = !{i64 10}
509+
; CHECK: [[META6]] = !{}
510+
; CHECK: [[META7]] = !{i64 10}
435511
; CHECK: [[RNG8]] = !{i64 0, i64 10}
436512
; CHECK: [[RNG9]] = !{i64 0, i64 10, i64 20, i64 30}
437513
; CHECK: [[RNG10]] = !{i64 10, i64 30}

0 commit comments

Comments
 (0)