Skip to content

[compiler-rt][asan] Add asan checks for __builtin_assume_dereferencable#190871

Open
PiJoules wants to merge 1 commit intollvm:mainfrom
PiJoules:builtin-assume-dereferencable-asan
Open

[compiler-rt][asan] Add asan checks for __builtin_assume_dereferencable#190871
PiJoules wants to merge 1 commit intollvm:mainfrom
PiJoules:builtin-assume-dereferencable-asan

Conversation

@PiJoules
Copy link
Copy Markdown
Contributor

@PiJoules PiJoules commented Apr 7, 2026

This checks that the range covered by this intrinsic is dereferencable. Specifically it checks for llvm.assume intrinsics using the dereferencable operator bundle and just asserts the shadow for this range is zero.

The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability.

This checks that the range covered by this intrinsic is dereferencable.
Specifically it checks for `llvm.assume` intrinsics using the
`dereferencable` operator bundle and just asserts the shadow for this
range is zero.

The bulk of this PR was made by gemini but it was thoroughly edited and
reviewed to the best of my ability.
@PiJoules
Copy link
Copy Markdown
Contributor Author

PiJoules commented Apr 7, 2026

In hindsight it probably would've been better to make an RFC but I don't think I wasted a lot of time making this.

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 7, 2026

@llvm/pr-subscribers-llvm-transforms

Author: PiJoules

Changes

This checks that the range covered by this intrinsic is dereferencable. Specifically it checks for llvm.assume intrinsics using the dereferencable operator bundle and just asserts the shadow for this range is zero.

The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability.


Full diff: https://github.com/llvm/llvm-project/pull/190871.diff

13 Files Affected:

  • (modified) compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt (+2)
  • (modified) compiler-rt/lib/asan/asan_errors.cpp (+55)
  • (modified) compiler-rt/lib/asan/asan_errors.h (+12)
  • (modified) compiler-rt/lib/asan/asan_interface.inc (+2)
  • (modified) compiler-rt/lib/asan/asan_report.cpp (+12)
  • (modified) compiler-rt/lib/asan/asan_report.h (+2)
  • (modified) compiler-rt/lib/asan/asan_rtl.cpp (+16)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp (+96)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp (+14)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp (+24)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp (+35)
  • (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+40)
  • (added) llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll (+18)
diff --git a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
index 5efc48c262369..d3b8e98bd020f 100644
--- a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
+++ b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
@@ -1,5 +1,7 @@
 #! .
 __asan_report_load_n
+__asan_report_assume_dereferenceable
+__asan_report_assume_dereferenceable_noabort
 __asan_loadN
 __asan_report_load1
 __asan_load1
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index c4100cf7244e3..91941fb18dafb 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -713,4 +713,59 @@ void ErrorGeneric::Print() {
   CheckPoisonRecords(addr);
 }
 
+ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc,
+                                                       uptr bp, uptr sp,
+                                                       uptr addr,
+                                                       uptr dereferencable_size)
+    : ErrorBase(tid),
+      addr_description(addr, dereferencable_size,
+                       /*shouldLockThreadRegistry=*/false),
+      pc(pc),
+      bp(bp),
+      sp(sp),
+      dereferencable_size(dereferencable_size) {
+  scariness.Clear();
+  scariness.Scare(10, "assume-dereferenceable");
+}
+
+void ErrorAssumeDereferenceable::Print() {
+  Decorator d;
+  Printf("%s", d.Error());
+  uptr addr = addr_description.Address();
+  Report(
+      "ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at "
+      "pc %p bp %p sp %p\n",
+      (void*)addr, (void*)pc, (void*)bp, (void*)sp);
+  Printf("%s", d.Default());
+
+  Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(),
+         dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(),
+         d.Default());
+
+  uptr range_start = addr;
+  bool current_poisoned = AddressIsPoisoned(range_start);
+  for (uptr i = 1; i <= dereferencable_size; ++i) {
+    bool poisoned = (i < dereferencable_size) ? AddressIsPoisoned(addr + i)
+                                              : !current_poisoned;
+    if (poisoned != current_poisoned) {
+      Printf("%s  range [%p, %p) is %s%s\n", d.Default(), (void*)range_start,
+             (void*)(addr + i),
+             current_poisoned ? "NOT dereferenceable" : "dereferenceable",
+             d.Default());
+      if (i < dereferencable_size) {
+        range_start = addr + i;
+        current_poisoned = poisoned;
+      }
+    }
+  }
+
+  scariness.Print();
+  GET_STACK_TRACE_FATAL(pc, bp);
+  stack.Print();
+
+  addr_description.Print("assume-dereferenceable-failed");
+  ReportErrorSummary("assume-dereferenceable-failed", &stack);
+  PrintShadowMemoryForAddress(addr);
+}
+
 }  // namespace __asan
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index 9c6843774f376..660250246f91b 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -436,6 +436,17 @@ struct ErrorGeneric : ErrorBase {
   void Print();
 };
 
+struct ErrorAssumeDereferenceable : ErrorBase {
+  AddressDescription addr_description;
+  uptr pc, bp, sp;
+  uptr dereferencable_size;
+
+  ErrorAssumeDereferenceable() = default;  // (*)
+  ErrorAssumeDereferenceable(u32 tid, uptr pc, uptr bp, uptr sp, uptr addr,
+                             uptr dereferencable_size);
+  void Print();
+};
+
 // clang-format off
 #define ASAN_FOR_EACH_ERROR_KIND(macro)                    \
   macro(DeadlySignal)                                      \
@@ -462,6 +473,7 @@ struct ErrorGeneric : ErrorBase {
   macro(BadParamsToCopyContiguousContainerAnnotations)     \
   macro(ODRViolation)                                      \
   macro(InvalidPointerPair)                                \
+  macro(AssumeDereferenceable)                             \
   macro(Generic)
 // clang-format on
 
diff --git a/compiler-rt/lib/asan/asan_interface.inc b/compiler-rt/lib/asan/asan_interface.inc
index b465356b1e443..3c794376fa959 100644
--- a/compiler-rt/lib/asan/asan_interface.inc
+++ b/compiler-rt/lib/asan/asan_interface.inc
@@ -93,6 +93,8 @@ INTERFACE_FUNCTION(__asan_report_load8_noabort)
 INTERFACE_FUNCTION(__asan_report_load16_noabort)
 INTERFACE_FUNCTION(__asan_report_load_n_noabort)
 INTERFACE_FUNCTION(__asan_report_present)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable_noabort)
 INTERFACE_FUNCTION(__asan_report_store1)
 INTERFACE_FUNCTION(__asan_report_store2)
 INTERFACE_FUNCTION(__asan_report_store4)
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index e50faf0223212..fbdada1849d45 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -543,6 +543,18 @@ void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
   in_report.ReportError(error);
 }
 
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+                                      uptr dereferencable_size, bool fatal) {
+  if (!fatal && SuppressErrorReport(pc))
+    return;
+  ENABLE_FRAME_POINTER;
+
+  ScopedInErrorReport in_report(fatal);
+  ErrorAssumeDereferenceable error(GetCurrentTidOrInvalid(), pc, bp, sp, addr,
+                                   dereferencable_size);
+  in_report.ReportError(error);
+}
+
 }  // namespace __asan
 
 // --------------------------- Interface --------------------- {{{1
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index b181849b10f92..a0d16e50d02fb 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -49,6 +49,8 @@ bool ParseFrameDescription(const char *frame_descr,
 // Different kinds of error reports.
 void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
                         uptr access_size, u32 exp, bool fatal);
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+                                      uptr dereferencable_size, bool fatal);
 void ReportDeadlySignal(const SignalContext &sig);
 void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
                                  uptr delete_alignment,
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index c036a13a11029..76e6b90b79524 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,6 +156,22 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) {           \
 ASAN_REPORT_ERROR_N(load, false)
 ASAN_REPORT_ERROR_N(store, true)
 
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+  if (__asan_region_is_poisoned(addr, size)) {
+    GET_CALLER_PC_BP_SP;
+    ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
+  }
+}
+
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+  if (__asan_region_is_poisoned(addr, size)) {
+    GET_CALLER_PC_BP_SP;
+    ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
+  }
+}
+
 #define ASAN_MEMORY_ACCESS_CALLBACK_BODY(type, is_write, size, exp_arg, fatal) \
   uptr sp = MEM_TO_SHADOW(addr);                                               \
   uptr s = size <= ASAN_SHADOW_GRANULARITY ? *reinterpret_cast<u8 *>(sp)       \
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
new file mode 100644
index 0000000000000..f32e6d47c7971
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
@@ -0,0 +1,96 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t && %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void test_malloc_fully_oob() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_fully_oob: %p\n", p);
+  // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]]
+  // CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0
+  // CHECK-NEXT: range [[[PTR1]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+}
+
+void test_malloc_partial_right() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_partial_right: %p\n", p + 5);
+  // CHECK: test_malloc_partial_right: [[PTR2:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR2]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR2]] thread T0
+  // CHECK-NEXT: range [[[PTR2]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p + 5, 10);
+  free(p);
+}
+
+void test_malloc_partial_left() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_partial_left: %p\n", p - 5);
+  // CHECK: test_malloc_partial_left: [[PTR3:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR3]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR3]] thread T0
+  // CHECK-NEXT: range [[[PTR3]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+  __builtin_assume_dereferenceable(p - 5, 10);
+  free(p);
+}
+
+void test_stack_fully_oob() {
+  char p[10];
+  fprintf(stderr, "test_stack_fully_oob: %p\n", p);
+  // CHECK: test_stack_fully_oob: [[PTR4:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR4]]
+  // CHECK: ASSUMPTION of size 20 at [[PTR4]] thread T0
+  // CHECK-NEXT: range [[[PTR4]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_partial_right() {
+  char p[10];
+  fprintf(stderr, "test_stack_partial_right: %p\n", p + 5);
+  // CHECK: test_stack_partial_right: [[PTR5:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR5]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR5]] thread T0
+  // CHECK-NEXT: range [[[PTR5]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p + 5, 10);
+}
+
+void test_stack_partial_left() {
+  char p[10];
+  fprintf(stderr, "test_stack_partial_left: %p\n", p - 5);
+  // CHECK: test_stack_partial_left: [[PTR6:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR6]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR6]] thread T0
+  // CHECK-NEXT: range [[[PTR6]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+  __builtin_assume_dereferenceable(p - 5, 10);
+}
+
+void test_malloc_completely_poisoned() {
+  char *p = (char *)malloc(10);
+  free(p);
+  fprintf(stderr, "test_malloc_completely_poisoned: %p\n", p);
+  // CHECK: test_malloc_completely_poisoned: [[PTR7:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR7]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR7]] thread T0
+  // CHECK-NEXT: range [[[PTR7]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NOT: is dereferenceable
+  __builtin_assume_dereferenceable(p, 10);
+}
+
+int main() {
+  test_malloc_fully_oob();
+  test_malloc_partial_right();
+  test_malloc_partial_left();
+  test_stack_fully_oob();
+  test_stack_partial_right();
+  test_stack_partial_left();
+  test_malloc_completely_poisoned();
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
new file mode 100644
index 0000000000000..a1abf837619b1
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
@@ -0,0 +1,14 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main() {
+  char *p = (char *)malloc(10);
+  free(p);
+  // CHECK: AddressSanitizer: assume-dereferenceable-failed
+  // CHECK: ASSUMPTION of size 10
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NOT: is dereferenceable
+  __builtin_assume_dereferenceable(p, 10);
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
new file mode 100644
index 0000000000000..86cd92580ce38
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
@@ -0,0 +1,24 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t
+// RUN: %env_asan_opts=halt_on_error=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "PTR: %p\n", p);
+  // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+
+  // CHECK-RECOVER: PTR: [[PTR:0x[0-9a-f]+]]
+  // CHECK-RECOVER: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+
+  fprintf(stderr, "EXECUTED AFTER ERROR\n");
+  // CHECK-NOT: EXECUTED AFTER ERROR
+  // CHECK-RECOVER: EXECUTED AFTER ERROR
+
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
new file mode 100644
index 0000000000000..9f5a65888df3b
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
@@ -0,0 +1,35 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && %run %t
+
+#include <stdlib.h>
+
+void test_pass_1() {
+  char *p = (char *)malloc(20);
+  __builtin_assume_dereferenceable(p, 10);
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+}
+
+void test_pass_2() {
+  char *p = (char *)malloc(10);
+  __builtin_assume_dereferenceable(p, 0);
+  free(p);
+}
+
+void test_stack_pass_1() {
+  char p[20];
+  __builtin_assume_dereferenceable(p, 10);
+  __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_pass_2() {
+  char p[10];
+  __builtin_assume_dereferenceable(p, 0);
+}
+
+int main() {
+  test_pass_1();
+  test_pass_2();
+  test_stack_pass_1();
+  test_stack_pass_2();
+  return 0;
+}
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 8b5969ffb3ca0..4d575b3defecf 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -299,6 +299,11 @@ static cl::opt<bool> ClRedzoneByvalArgs("asan-redzone-byval-args",
                                                  "required)"), cl::Hidden,
                                         cl::init(true));
 
+static cl::opt<bool> ClInstrumentAssumeDereferenceable(
+    "asan-instrument-assume-dereferenceable",
+    cl::desc("instrument llvm.assume(dereferenceable)"), cl::Hidden,
+    cl::init(true));
+
 static cl::opt<bool> ClUseAfterScope("asan-use-after-scope",
                                      cl::desc("Check stack-use-after-scope"),
                                      cl::Hidden, cl::init(false));
@@ -924,6 +929,7 @@ struct AddressSanitizer {
   // These arrays is indexed by AccessIsWrite and Experiment.
   FunctionCallee AsanErrorCallbackSized[2][2];
   FunctionCallee AsanMemoryAccessCallbackSized[2][2];
+  FunctionCallee AsanAssumeDereferenceableCallback;
 
   FunctionCallee AsanMemmove, AsanMemcpy, AsanMemset;
   Value *LocalDynamicShadow = nullptr;
@@ -2881,6 +2887,12 @@ bool ModuleAddressSanitizer::instrumentModule() {
 
 void AddressSanitizer::initializeCallbacks(const TargetLibraryInfo *TLI) {
   IRBuilder<> IRB(*C);
+
+  const std::string EndingStr = Recover ? "_noabort" : "";
+  AsanAssumeDereferenceableCallback = M.getOrInsertFunction(
+      "__asan_report_assume_dereferenceable" + EndingStr,
+      FunctionType::get(IRB.getVoidTy(), {IntptrTy, IntptrTy}, false));
+
   // Create __asan_report* callbacks.
   // IsWrite, TypeSize and Exp are encoded in the function name.
   for (int Exp = 0; Exp < 2; Exp++) {
@@ -3099,6 +3111,7 @@ bool AddressSanitizer::instrumentFunction(Function &F,
   SmallVector<Instruction *, 8> NoReturnCalls;
   SmallVector<BasicBlock *, 16> AllBlocks;
   SmallVector<Instruction *, 16> PointerComparisonsOrSubtracts;
+  SmallVector<AssumeInst *, 8> DerefAssumptions;
 
   // Fill the set of memory operations to instrument.
   for (auto &BB : F) {
@@ -3140,6 +3153,12 @@ bool AddressSanitizer::instrumentFunction(Function &F,
         // ok, take it.
         IntrinToInstrument.push_back(MI);
         NumInsnsPerBB++;
+      } else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) {
+        auto *AI = cast<AssumeInst>(&Inst);
+        if (AI->getOperandBundle("dereferenceable")) {
+          DerefAssumptions.push_back(AI);
+          NumInsnsPerBB++;
+        }
       } else {
         if (auto *CB = dyn_cast<CallBase>(&Inst)) {
           // A call inside BB.
@@ -3189,6 +3208,27 @@ bool AddressSanitizer::instrumentFunction(Function &F,
     FunctionModified = true;
   }
 
+  for (auto *AI : DerefAssumptions) {
+    if (auto Bundle = AI->getOperandBundle("dereferenceable")) {
+      Value *Ptr = Bundle->Inputs[0];
+      Value *Size = Bundle->Inputs[1];
+
+      InstrumentationIRBuilder IRB(AI);
+      Value *AddrLong = IRB.CreatePointerCast(Ptr, IntptrTy);
+      Value *SizeExt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
+
+      // Skip if size is exactly 0.
+      if (ConstantInt *CI = dyn_cast<ConstantInt>(SizeExt)) {
+        if (CI->isZero())
+          continue;
+      }
+
+      RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+
+      FunctionModified = true;
+    }
+  }
+
   if (ChangedStack || !NoReturnCalls.empty())
     FunctionModified = true;
 
diff --git a/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
new file mode 100644
index 0000000000000..cb0d69da60639
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=asan -asan-instrument-assume-dereferenceable=1 -S | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @test(ptr %p, i64 %size) sanitize_address {
+; CHECK-LABEL: @test(
+; CHECK-NEXT:    [[TMP17:%.*]] = ptrtoint ptr [[TMP4:%.*]] to i64
+; CHECK-NEXT:    call void @__asan_report_assume_dereferenceable(i64 [[TMP17]], i64 [[SIZE:%.*]])
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP4]], i64 [[SIZE]]) ]
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 %size) ]
+  ret void
+}
+
+declare void @llvm.assume(i1)

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 7, 2026

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: PiJoules

Changes

This checks that the range covered by this intrinsic is dereferencable. Specifically it checks for llvm.assume intrinsics using the dereferencable operator bundle and just asserts the shadow for this range is zero.

The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability.


Full diff: https://github.com/llvm/llvm-project/pull/190871.diff

13 Files Affected:

  • (modified) compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt (+2)
  • (modified) compiler-rt/lib/asan/asan_errors.cpp (+55)
  • (modified) compiler-rt/lib/asan/asan_errors.h (+12)
  • (modified) compiler-rt/lib/asan/asan_interface.inc (+2)
  • (modified) compiler-rt/lib/asan/asan_report.cpp (+12)
  • (modified) compiler-rt/lib/asan/asan_report.h (+2)
  • (modified) compiler-rt/lib/asan/asan_rtl.cpp (+16)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp (+96)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp (+14)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp (+24)
  • (added) compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp (+35)
  • (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+40)
  • (added) llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll (+18)
diff --git a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
index 5efc48c262369..d3b8e98bd020f 100644
--- a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
+++ b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
@@ -1,5 +1,7 @@
 #! .
 __asan_report_load_n
+__asan_report_assume_dereferenceable
+__asan_report_assume_dereferenceable_noabort
 __asan_loadN
 __asan_report_load1
 __asan_load1
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index c4100cf7244e3..91941fb18dafb 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -713,4 +713,59 @@ void ErrorGeneric::Print() {
   CheckPoisonRecords(addr);
 }
 
+ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc,
+                                                       uptr bp, uptr sp,
+                                                       uptr addr,
+                                                       uptr dereferencable_size)
+    : ErrorBase(tid),
+      addr_description(addr, dereferencable_size,
+                       /*shouldLockThreadRegistry=*/false),
+      pc(pc),
+      bp(bp),
+      sp(sp),
+      dereferencable_size(dereferencable_size) {
+  scariness.Clear();
+  scariness.Scare(10, "assume-dereferenceable");
+}
+
+void ErrorAssumeDereferenceable::Print() {
+  Decorator d;
+  Printf("%s", d.Error());
+  uptr addr = addr_description.Address();
+  Report(
+      "ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at "
+      "pc %p bp %p sp %p\n",
+      (void*)addr, (void*)pc, (void*)bp, (void*)sp);
+  Printf("%s", d.Default());
+
+  Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(),
+         dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(),
+         d.Default());
+
+  uptr range_start = addr;
+  bool current_poisoned = AddressIsPoisoned(range_start);
+  for (uptr i = 1; i <= dereferencable_size; ++i) {
+    bool poisoned = (i < dereferencable_size) ? AddressIsPoisoned(addr + i)
+                                              : !current_poisoned;
+    if (poisoned != current_poisoned) {
+      Printf("%s  range [%p, %p) is %s%s\n", d.Default(), (void*)range_start,
+             (void*)(addr + i),
+             current_poisoned ? "NOT dereferenceable" : "dereferenceable",
+             d.Default());
+      if (i < dereferencable_size) {
+        range_start = addr + i;
+        current_poisoned = poisoned;
+      }
+    }
+  }
+
+  scariness.Print();
+  GET_STACK_TRACE_FATAL(pc, bp);
+  stack.Print();
+
+  addr_description.Print("assume-dereferenceable-failed");
+  ReportErrorSummary("assume-dereferenceable-failed", &stack);
+  PrintShadowMemoryForAddress(addr);
+}
+
 }  // namespace __asan
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index 9c6843774f376..660250246f91b 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -436,6 +436,17 @@ struct ErrorGeneric : ErrorBase {
   void Print();
 };
 
+struct ErrorAssumeDereferenceable : ErrorBase {
+  AddressDescription addr_description;
+  uptr pc, bp, sp;
+  uptr dereferencable_size;
+
+  ErrorAssumeDereferenceable() = default;  // (*)
+  ErrorAssumeDereferenceable(u32 tid, uptr pc, uptr bp, uptr sp, uptr addr,
+                             uptr dereferencable_size);
+  void Print();
+};
+
 // clang-format off
 #define ASAN_FOR_EACH_ERROR_KIND(macro)                    \
   macro(DeadlySignal)                                      \
@@ -462,6 +473,7 @@ struct ErrorGeneric : ErrorBase {
   macro(BadParamsToCopyContiguousContainerAnnotations)     \
   macro(ODRViolation)                                      \
   macro(InvalidPointerPair)                                \
+  macro(AssumeDereferenceable)                             \
   macro(Generic)
 // clang-format on
 
diff --git a/compiler-rt/lib/asan/asan_interface.inc b/compiler-rt/lib/asan/asan_interface.inc
index b465356b1e443..3c794376fa959 100644
--- a/compiler-rt/lib/asan/asan_interface.inc
+++ b/compiler-rt/lib/asan/asan_interface.inc
@@ -93,6 +93,8 @@ INTERFACE_FUNCTION(__asan_report_load8_noabort)
 INTERFACE_FUNCTION(__asan_report_load16_noabort)
 INTERFACE_FUNCTION(__asan_report_load_n_noabort)
 INTERFACE_FUNCTION(__asan_report_present)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable_noabort)
 INTERFACE_FUNCTION(__asan_report_store1)
 INTERFACE_FUNCTION(__asan_report_store2)
 INTERFACE_FUNCTION(__asan_report_store4)
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index e50faf0223212..fbdada1849d45 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -543,6 +543,18 @@ void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
   in_report.ReportError(error);
 }
 
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+                                      uptr dereferencable_size, bool fatal) {
+  if (!fatal && SuppressErrorReport(pc))
+    return;
+  ENABLE_FRAME_POINTER;
+
+  ScopedInErrorReport in_report(fatal);
+  ErrorAssumeDereferenceable error(GetCurrentTidOrInvalid(), pc, bp, sp, addr,
+                                   dereferencable_size);
+  in_report.ReportError(error);
+}
+
 }  // namespace __asan
 
 // --------------------------- Interface --------------------- {{{1
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index b181849b10f92..a0d16e50d02fb 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -49,6 +49,8 @@ bool ParseFrameDescription(const char *frame_descr,
 // Different kinds of error reports.
 void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
                         uptr access_size, u32 exp, bool fatal);
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+                                      uptr dereferencable_size, bool fatal);
 void ReportDeadlySignal(const SignalContext &sig);
 void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
                                  uptr delete_alignment,
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index c036a13a11029..76e6b90b79524 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,6 +156,22 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) {           \
 ASAN_REPORT_ERROR_N(load, false)
 ASAN_REPORT_ERROR_N(store, true)
 
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+  if (__asan_region_is_poisoned(addr, size)) {
+    GET_CALLER_PC_BP_SP;
+    ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
+  }
+}
+
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+  if (__asan_region_is_poisoned(addr, size)) {
+    GET_CALLER_PC_BP_SP;
+    ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
+  }
+}
+
 #define ASAN_MEMORY_ACCESS_CALLBACK_BODY(type, is_write, size, exp_arg, fatal) \
   uptr sp = MEM_TO_SHADOW(addr);                                               \
   uptr s = size <= ASAN_SHADOW_GRANULARITY ? *reinterpret_cast<u8 *>(sp)       \
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
new file mode 100644
index 0000000000000..f32e6d47c7971
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
@@ -0,0 +1,96 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t && %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void test_malloc_fully_oob() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_fully_oob: %p\n", p);
+  // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]]
+  // CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0
+  // CHECK-NEXT: range [[[PTR1]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+}
+
+void test_malloc_partial_right() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_partial_right: %p\n", p + 5);
+  // CHECK: test_malloc_partial_right: [[PTR2:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR2]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR2]] thread T0
+  // CHECK-NEXT: range [[[PTR2]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p + 5, 10);
+  free(p);
+}
+
+void test_malloc_partial_left() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "test_malloc_partial_left: %p\n", p - 5);
+  // CHECK: test_malloc_partial_left: [[PTR3:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR3]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR3]] thread T0
+  // CHECK-NEXT: range [[[PTR3]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+  __builtin_assume_dereferenceable(p - 5, 10);
+  free(p);
+}
+
+void test_stack_fully_oob() {
+  char p[10];
+  fprintf(stderr, "test_stack_fully_oob: %p\n", p);
+  // CHECK: test_stack_fully_oob: [[PTR4:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR4]]
+  // CHECK: ASSUMPTION of size 20 at [[PTR4]] thread T0
+  // CHECK-NEXT: range [[[PTR4]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_partial_right() {
+  char p[10];
+  fprintf(stderr, "test_stack_partial_right: %p\n", p + 5);
+  // CHECK: test_stack_partial_right: [[PTR5:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR5]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR5]] thread T0
+  // CHECK-NEXT: range [[[PTR5]], 0x{{.*}}) is dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  __builtin_assume_dereferenceable(p + 5, 10);
+}
+
+void test_stack_partial_left() {
+  char p[10];
+  fprintf(stderr, "test_stack_partial_left: %p\n", p - 5);
+  // CHECK: test_stack_partial_left: [[PTR6:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR6]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR6]] thread T0
+  // CHECK-NEXT: range [[[PTR6]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+  __builtin_assume_dereferenceable(p - 5, 10);
+}
+
+void test_malloc_completely_poisoned() {
+  char *p = (char *)malloc(10);
+  free(p);
+  fprintf(stderr, "test_malloc_completely_poisoned: %p\n", p);
+  // CHECK: test_malloc_completely_poisoned: [[PTR7:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR7]]
+  // CHECK: ASSUMPTION of size 10 at [[PTR7]] thread T0
+  // CHECK-NEXT: range [[[PTR7]], 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NOT: is dereferenceable
+  __builtin_assume_dereferenceable(p, 10);
+}
+
+int main() {
+  test_malloc_fully_oob();
+  test_malloc_partial_right();
+  test_malloc_partial_left();
+  test_stack_fully_oob();
+  test_stack_partial_right();
+  test_stack_partial_left();
+  test_malloc_completely_poisoned();
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
new file mode 100644
index 0000000000000..a1abf837619b1
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
@@ -0,0 +1,14 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main() {
+  char *p = (char *)malloc(10);
+  free(p);
+  // CHECK: AddressSanitizer: assume-dereferenceable-failed
+  // CHECK: ASSUMPTION of size 10
+  // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+  // CHECK-NOT: is dereferenceable
+  __builtin_assume_dereferenceable(p, 10);
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
new file mode 100644
index 0000000000000..86cd92580ce38
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
@@ -0,0 +1,24 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t
+// RUN: %env_asan_opts=halt_on_error=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  char *p = (char *)malloc(10);
+  fprintf(stderr, "PTR: %p\n", p);
+  // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
+  // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+
+  // CHECK-RECOVER: PTR: [[PTR:0x[0-9a-f]+]]
+  // CHECK-RECOVER: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+
+  fprintf(stderr, "EXECUTED AFTER ERROR\n");
+  // CHECK-NOT: EXECUTED AFTER ERROR
+  // CHECK-RECOVER: EXECUTED AFTER ERROR
+
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
new file mode 100644
index 0000000000000..9f5a65888df3b
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
@@ -0,0 +1,35 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && %run %t
+
+#include <stdlib.h>
+
+void test_pass_1() {
+  char *p = (char *)malloc(20);
+  __builtin_assume_dereferenceable(p, 10);
+  __builtin_assume_dereferenceable(p, 20);
+  free(p);
+}
+
+void test_pass_2() {
+  char *p = (char *)malloc(10);
+  __builtin_assume_dereferenceable(p, 0);
+  free(p);
+}
+
+void test_stack_pass_1() {
+  char p[20];
+  __builtin_assume_dereferenceable(p, 10);
+  __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_pass_2() {
+  char p[10];
+  __builtin_assume_dereferenceable(p, 0);
+}
+
+int main() {
+  test_pass_1();
+  test_pass_2();
+  test_stack_pass_1();
+  test_stack_pass_2();
+  return 0;
+}
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 8b5969ffb3ca0..4d575b3defecf 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -299,6 +299,11 @@ static cl::opt<bool> ClRedzoneByvalArgs("asan-redzone-byval-args",
                                                  "required)"), cl::Hidden,
                                         cl::init(true));
 
+static cl::opt<bool> ClInstrumentAssumeDereferenceable(
+    "asan-instrument-assume-dereferenceable",
+    cl::desc("instrument llvm.assume(dereferenceable)"), cl::Hidden,
+    cl::init(true));
+
 static cl::opt<bool> ClUseAfterScope("asan-use-after-scope",
                                      cl::desc("Check stack-use-after-scope"),
                                      cl::Hidden, cl::init(false));
@@ -924,6 +929,7 @@ struct AddressSanitizer {
   // These arrays is indexed by AccessIsWrite and Experiment.
   FunctionCallee AsanErrorCallbackSized[2][2];
   FunctionCallee AsanMemoryAccessCallbackSized[2][2];
+  FunctionCallee AsanAssumeDereferenceableCallback;
 
   FunctionCallee AsanMemmove, AsanMemcpy, AsanMemset;
   Value *LocalDynamicShadow = nullptr;
@@ -2881,6 +2887,12 @@ bool ModuleAddressSanitizer::instrumentModule() {
 
 void AddressSanitizer::initializeCallbacks(const TargetLibraryInfo *TLI) {
   IRBuilder<> IRB(*C);
+
+  const std::string EndingStr = Recover ? "_noabort" : "";
+  AsanAssumeDereferenceableCallback = M.getOrInsertFunction(
+      "__asan_report_assume_dereferenceable" + EndingStr,
+      FunctionType::get(IRB.getVoidTy(), {IntptrTy, IntptrTy}, false));
+
   // Create __asan_report* callbacks.
   // IsWrite, TypeSize and Exp are encoded in the function name.
   for (int Exp = 0; Exp < 2; Exp++) {
@@ -3099,6 +3111,7 @@ bool AddressSanitizer::instrumentFunction(Function &F,
   SmallVector<Instruction *, 8> NoReturnCalls;
   SmallVector<BasicBlock *, 16> AllBlocks;
   SmallVector<Instruction *, 16> PointerComparisonsOrSubtracts;
+  SmallVector<AssumeInst *, 8> DerefAssumptions;
 
   // Fill the set of memory operations to instrument.
   for (auto &BB : F) {
@@ -3140,6 +3153,12 @@ bool AddressSanitizer::instrumentFunction(Function &F,
         // ok, take it.
         IntrinToInstrument.push_back(MI);
         NumInsnsPerBB++;
+      } else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) {
+        auto *AI = cast<AssumeInst>(&Inst);
+        if (AI->getOperandBundle("dereferenceable")) {
+          DerefAssumptions.push_back(AI);
+          NumInsnsPerBB++;
+        }
       } else {
         if (auto *CB = dyn_cast<CallBase>(&Inst)) {
           // A call inside BB.
@@ -3189,6 +3208,27 @@ bool AddressSanitizer::instrumentFunction(Function &F,
     FunctionModified = true;
   }
 
+  for (auto *AI : DerefAssumptions) {
+    if (auto Bundle = AI->getOperandBundle("dereferenceable")) {
+      Value *Ptr = Bundle->Inputs[0];
+      Value *Size = Bundle->Inputs[1];
+
+      InstrumentationIRBuilder IRB(AI);
+      Value *AddrLong = IRB.CreatePointerCast(Ptr, IntptrTy);
+      Value *SizeExt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
+
+      // Skip if size is exactly 0.
+      if (ConstantInt *CI = dyn_cast<ConstantInt>(SizeExt)) {
+        if (CI->isZero())
+          continue;
+      }
+
+      RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+
+      FunctionModified = true;
+    }
+  }
+
   if (ChangedStack || !NoReturnCalls.empty())
     FunctionModified = true;
 
diff --git a/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
new file mode 100644
index 0000000000000..cb0d69da60639
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=asan -asan-instrument-assume-dereferenceable=1 -S | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @test(ptr %p, i64 %size) sanitize_address {
+; CHECK-LABEL: @test(
+; CHECK-NEXT:    [[TMP17:%.*]] = ptrtoint ptr [[TMP4:%.*]] to i64
+; CHECK-NEXT:    call void @__asan_report_assume_dereferenceable(i64 [[TMP17]], i64 [[SIZE:%.*]])
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP4]], i64 [[SIZE]]) ]
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 %size) ]
+  ret void
+}
+
+declare void @llvm.assume(i1)

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions inc,h,cpp -- compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp compiler-rt/lib/asan/asan_errors.cpp compiler-rt/lib/asan/asan_errors.h compiler-rt/lib/asan/asan_interface.inc compiler-rt/lib/asan/asan_report.cpp compiler-rt/lib/asan/asan_report.h compiler-rt/lib/asan/asan_rtl.cpp llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index 76e6b90b7..9726f8f44 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,16 +156,16 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) {           \
 ASAN_REPORT_ERROR_N(load, false)
 ASAN_REPORT_ERROR_N(store, true)
 
-extern "C" NOINLINE INTERFACE_ATTRIBUTE
-void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+extern "C" NOINLINE INTERFACE_ATTRIBUTE void
+__asan_report_assume_dereferenceable(uptr addr, uptr size) {
   if (__asan_region_is_poisoned(addr, size)) {
     GET_CALLER_PC_BP_SP;
     ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
   }
 }
 
-extern "C" NOINLINE INTERFACE_ATTRIBUTE
-void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+extern "C" NOINLINE INTERFACE_ATTRIBUTE void
+__asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
   if (__asan_region_is_poisoned(addr, size)) {
     GET_CALLER_PC_BP_SP;
     ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 4d575b3de..60996ca07 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -3223,7 +3223,8 @@ bool AddressSanitizer::instrumentFunction(Function &F,
           continue;
       }
 
-      RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+      RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback,
+                             {AddrLong, SizeExt});
 
       FunctionModified = true;
     }

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🪟 Windows x64 Test Results

  • 134771 tests passed
  • 4406 tests skipped
  • 4 tests failed

Failed Tests

(click on a test name to see its output)

AddressSanitizer-x86_64-windows

AddressSanitizer-x86_64-windows.TestCases/assume_dereferenceable.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
C:/_work/llvm-project/llvm-project/build/./bin/clang.exe  -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp -o C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable.cpp.tmp && env ASAN_OPTIONS=halt_on_error=0  C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable.cpp.tmp 2>&1 | FileCheck C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp
# executed command: C:/_work/llvm-project/llvm-project/build/./bin/clang.exe -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp' -o 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: env ASAN_OPTIONS=halt_on_error=0 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: FileCheck 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp:9:12: error: CHECK: expected string not found in input
# |  // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
# |            ^
# | <stdin>:1:1: note: scanning from here
# | test_malloc_fully_oob: 000011EB098A0030
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          1: test_malloc_fully_oob: 000011EB098A0030 
# | check:9     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |          2: ================================================================= 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          3: ==21772==ERROR: AddressSanitizer: assume-dereferenceable-failed on address 0x11eb098a0030 at pc 0x7ff631a9104b bp 0x001d6eaffb50 sp 0x001d6eaffb88 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          4: ASSUMPTION of size 20 at 0x11eb098a0030 thread T0 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          5:  range [0x11eb098a0030, 0x11eb098a003a) is dereferenceable 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          6:  range [0x11eb098a003a, 0x11eb098a0044) is NOT dereferenceable 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

AddressSanitizer-x86_64-windows.TestCases/assume_dereferenceable_halt_on_error.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
C:/_work/llvm-project/llvm-project/build/./bin/clang.exe  -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp -o C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp
# executed command: C:/_work/llvm-project/llvm-project/build/./bin/clang.exe -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp' -o 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp'
# note: command had no output on stdout or stderr
# RUN: at line 2
env ASAN_OPTIONS=halt_on_error=1 not  C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp 2>&1 | FileCheck C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp
# executed command: env ASAN_OPTIONS=halt_on_error=1 not 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: FileCheck 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp:11:12: error: CHECK: expected string not found in input
# |  // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
# |            ^
# | <stdin>:1:1: note: scanning from here
# | PTR: 000011A3C57A0030
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |           1: PTR: 000011A3C57A0030 
# | check:11     X~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |           2: ================================================================= 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           3: ==22396==ERROR: AddressSanitizer: assume-dereferenceable-failed on address 0x11a3c57a0030 at pc 0x7ff793231052 bp 0x00565a5afbb0 sp 0x00565a5afbe8 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           4: ASSUMPTION of size 20 at 0x11a3c57a0030 thread T0 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           5:  range [0x11a3c57a0030, 0x11a3c57a003a) is dereferenceable 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           6:  range [0x11a3c57a003a, 0x11a3c57a0044) is NOT dereferenceable 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           .
# |           .
# |           .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

AddressSanitizer-x86_64-windows-dynamic

AddressSanitizer-x86_64-windows-dynamic.TestCases/assume_dereferenceable.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
C:/_work/llvm-project/llvm-project/build/./bin/clang.exe  -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   -shared-libasan -D_MT -D_DLL -Wl,-nodefaultlib:libcmt,-defaultlib:msvcrt,-defaultlib:oldnames -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp -o C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable.cpp.tmp && env ASAN_OPTIONS=halt_on_error=0  C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable.cpp.tmp 2>&1 | FileCheck C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp
# executed command: C:/_work/llvm-project/llvm-project/build/./bin/clang.exe -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta -shared-libasan -D_MT -D_DLL -Wl,-nodefaultlib:libcmt,-defaultlib:msvcrt,-defaultlib:oldnames -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp' -o 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: env ASAN_OPTIONS=halt_on_error=0 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: FileCheck 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp:9:12: error: CHECK: expected string not found in input
# |  // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
# |            ^
# | <stdin>:1:1: note: scanning from here
# | test_malloc_fully_oob: 000011D3E71A0010
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          1: test_malloc_fully_oob: 000011D3E71A0010 
# | check:9     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |          2: ================================================================= 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          3: ==17344==ERROR: AddressSanitizer: assume-dereferenceable-failed on address 0x11d3e71a0010 at pc 0x7ff7e4621053 bp 0x00108e4ffdb0 sp 0x00108e4ffde8 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          4: ASSUMPTION of size 20 at 0x11d3e71a0010 thread T0 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          5:  range [0x11d3e71a0010, 0x11d3e71a001a) is dereferenceable 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          6:  range [0x11d3e71a001a, 0x11d3e71a0024) is NOT dereferenceable 
# | check:9     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

AddressSanitizer-x86_64-windows-dynamic.TestCases/assume_dereferenceable_halt_on_error.cpp
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
C:/_work/llvm-project/llvm-project/build/./bin/clang.exe  -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   -shared-libasan -D_MT -D_DLL -Wl,-nodefaultlib:libcmt,-defaultlib:msvcrt,-defaultlib:oldnames -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp -o C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp
# executed command: C:/_work/llvm-project/llvm-project/build/./bin/clang.exe -fsanitize=address -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -gline-tables-only -gcodeview -gcolumn-info -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta -shared-libasan -D_MT -D_DLL -Wl,-nodefaultlib:libcmt,-defaultlib:msvcrt,-defaultlib:oldnames -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp' -o 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp'
# note: command had no output on stdout or stderr
# RUN: at line 2
env ASAN_OPTIONS=halt_on_error=1 not  C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp 2>&1 | FileCheck C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp
# executed command: env ASAN_OPTIONS=halt_on_error=1 not 'C:\_work\llvm-project\llvm-project\build\runtimes\runtimes-bins\compiler-rt\test\asan\X86_64WindowsDynamicConfig\TestCases\Output\assume_dereferenceable_halt_on_error.cpp.tmp'
# note: command had no output on stdout or stderr
# executed command: FileCheck 'C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp:11:12: error: CHECK: expected string not found in input
# |  // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
# |            ^
# | <stdin>:1:1: note: scanning from here
# | PTR: 000012A31B0A0010
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\compiler-rt\test\asan\TestCases\assume_dereferenceable_halt_on_error.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |           1: PTR: 000012A31B0A0010 
# | check:11     X~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |           2: ================================================================= 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           3: ==8420==ERROR: AddressSanitizer: assume-dereferenceable-failed on address 0x12a31b0a0010 at pc 0x7ff6f211105a bp 0x00bf45cffe40 sp 0x00bf45cffe78 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           4: ASSUMPTION of size 20 at 0x12a31b0a0010 thread T0 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           5:  range [0x12a31b0a0010, 0x12a31b0a001a) is dereferenceable 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           6:  range [0x12a31b0a001a, 0x12a31b0a0024) is NOT dereferenceable 
# | check:11     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           .
# |           .
# |           .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

IntrinToInstrument.push_back(MI);
NumInsnsPerBB++;
} else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) {
auto *AI = cast<AssumeInst>(&Inst);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

if (auto *AI = cast<AssumeInst>(&Inst))

fprintf(stderr, "test_malloc_fully_oob: %p\n", p);
// CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
// CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]]
// CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASSUMPTION of size 20 is not very meaningful. Maybe DEREFERENCABLE ASSUMPTION?

Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(),
dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(),
d.Default());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pull this into a helper?

(void*)(addr + i),
current_poisoned ? "NOT dereferenceable" : "dereferenceable",
d.Default());
if (i < dereferencable_size) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this loop be better if we jumped over the range here?

Printf("%s", d.Error());
uptr addr = addr_description.Address();
Report(
"ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at "
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional nit, just an idea: dereferencable-assumption-violation?

@fmayer
Copy link
Copy Markdown
Contributor

fmayer commented Apr 8, 2026

Please fix code formatting and tests

CheckPoisonRecords(addr);
}

ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one matches ErrorGeneric, we have other errors only for those which don't fit into ErrorGeneric at all

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is nice helpers to access generic error from user program _asan_get_report*
so I'd like to avoid either losing them or complicating them for a small customization

Please try to fit it ErrorGeneric case

void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
uptr access_size, u32 exp, bool fatal);
void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
uptr dereferencable_size, bool fatal);
Copy link
Copy Markdown
Collaborator

@vitalybuka vitalybuka Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo dereferencable_size -> dereferenceable_size

struct ErrorAssumeDereferenceable : ErrorBase {
AddressDescription addr_description;
uptr pc, bp, sp;
uptr dereferencable_size;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo dereferencable_size -> dereferenceable_size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants