[clang] [clang] add array out-of-bounds access constraints using llvm.assume (PR #159046)

Michael Kruse via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 6 02:48:01 PDT 2025


================
@@ -0,0 +1,91 @@
+// RUN: %clang_cc1 -emit-llvm -O2 -fassume-array-bounds %s -o - | FileCheck %s
+// Test that array bounds constraints are NOT applied to cases that might
+// break real-world code with intentional out-of-bounds access patterns.
+
+// C18 standard allows one-past-the-end pointers, and some legacy code
+// intentionally accesses out-of-bounds for performance or compatibility.
+// This test verifies that bounds constraints are only applied to safe cases.
+
+// CHECK-LABEL: define {{.*}} @test_flexible_array_member
+struct Data {
+    int count;
+    int items[1];  // Flexible array member pattern (pre-C99 style)
+};
+
+int test_flexible_array_member(struct Data *d, int i) {
+  // CHECK-NOT: call void @llvm.assume
+  // Flexible array member pattern (size 1 array as last field) should NOT
+  // generate bounds constraints because items[1] is just a placeholder
+  // for a larger array allocated with `malloc (sizeof (struct Data) + 42)`.
+  return d->items[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_not_flexible_array
+struct NotFlexible {
+    int items[1];  // Size 1 array but NOT the last field.
+    int count;     // Something comes after it.
+};
+
+int test_not_flexible_array(struct NotFlexible *s, int i) {
+  // CHECK: call void @llvm.assume
+  // This is NOT a flexible array pattern (not the last field),
+  // so we're fine generating `assume(i < 1)`.
+  return s->items[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_pointer_parameter
+int test_pointer_parameter(int *arr, int i) {
+  // CHECK-NOT: call void @llvm.assume
+  // Pointer parameters should NOT generate bounds constraints
+  // because we don't know the actual array size.
+  return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_vla
+int test_vla(int n, int i) {
+  int arr[n];  // Variable-length array.
+  // CHECK-NOT: call void @llvm.assume
+  // VLAs should NOT generate bounds constraints
+  // because the size is dynamic.
+  return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_one_past_end
+extern int extern_array[100];
+int *test_one_past_end(void) {
+  // CHECK-NOT: call void @llvm.assume
+  // Taking address of one-past-the-end is allowed by C standard.
+  // We should NOT assume anything about this access.
+  return &extern_array[100];  // Legal: one past the end.
----------------
Meinersbur wrote:

I tried
```cpp
extern int extern_array[100];
int *test_extern_array_val(int i) {
  return &extern_array[i];
}
```
with this PR an it generates
```llvm
  %bounds.constraint = icmp ult i32 %i, 100
  tail call void @llvm.assume(i1 %bounds.constraint)
```
if `&extern_array[100]` is legal, so must `test_extern_array_val(100)`.

Did you consider C++ references?
```
int &test_extern_array_val(int i) {
  return extern_array[i];
}
```
I think a reference must always point to valid memory, so here one can apply the stricter `i < 100`.


https://github.com/llvm/llvm-project/pull/159046


More information about the cfe-commits mailing list