[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