Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CODEGENOPT(ImplicitMapSyms, 1, 0, Benign) ///< -Wa,-mmapsyms=implicit
CODEGENOPT(AsmVerbose , 1, 0, Benign) ///< -dA, -fverbose-asm.
CODEGENOPT(PreserveAsmComments, 1, 1, Benign) ///< -dA, -fno-preserve-as-comments.
CODEGENOPT(AssumeSaneOperatorNew , 1, 1, Benign) ///< implicit __attribute__((malloc)) operator new
CODEGENOPT(AssumeArrayBounds , 1, 0, Benign) ///< Generate llvm.assume for array bounds.
CODEGENOPT(AssumeUniqueVTables , 1, 1, Benign) ///< Assume a class has only one vtable.
CODEGENOPT(Autolink , 1, 1, Benign) ///< -fno-autolink
CODEGENOPT(AutoImport , 1, 1, Benign) ///< -fno-auto-import
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,11 @@ defm assume_unique_vtables : BoolFOption<"assume-unique-vtables",
BothFlags<[], [ClangOption, CLOption]>>;

def fassume_sane_operator_new : Flag<["-"], "fassume-sane-operator-new">, Group<f_Group>;
defm assume_array_bounds : BoolFOption<"assume-array-bounds",
CodeGenOpts<"AssumeArrayBounds">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Generate llvm.assume for array bounds to enable optimizations (may break code with intentional out-of-bounds access)">,
NegFlag<SetFalse, [], [ClangOption, CC1Option]>>;
def fastcp : Flag<["-"], "fastcp">, Group<f_Group>;
def fastf : Flag<["-"], "fastf">, Group<f_Group>;
def fast : Flag<["-"], "fast">, Group<f_Group>;
Expand Down
171 changes: 171 additions & 0 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4559,6 +4559,157 @@ void CodeGenFunction::EmitCountedByBoundsChecking(
}
}

/// Emit array bounds constraints using llvm.assume for optimization hints.
///
/// C Standard (ISO/IEC 9899:2011 - C11)
/// Section J.2 (Undefined behavior): An array subscript is out of range, even
/// if an object is apparently accessible with the given subscript (as in the
/// lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).
///
/// Section 6.5.6 (Additive operators): If both the pointer operand and the
/// result point to elements of the same array object, or one past the last
/// element of the array object, the evaluation shall not produce an overflow;
/// otherwise, the behavior is undefined.
///
/// C++ Standard (ISO/IEC 14882 - 2017)
/// Section 8.7 (Additive operators):
/// 4 When an expression that has integral type is added to or subtracted from a
/// pointer, the result has the type of the pointer operand. If the expression
/// P points to element x[i] of an array object x with n elements,^86 the
/// expressions P + J and J + P (where J has the value j) point to the
/// (possibly-hypothetical) element x[i + j] if 0 ≤ i + j ≤ n; otherwise, the
/// behavior is undefined. Likewise, the expression P - J points to the
/// (possibly-hypothetical) element x[i − j] if 0 ≤ i − j ≤ n; otherwise, the
/// behavior is undefined.
/// ^86 A pointer past the last element of an array x of n elements is
/// considered to be equivalent to a pointer to a hypothetical element x[n]
/// for this purpose; see 6.9.2.
///

/// The standards allow &arr[size] (one-past-the-end) for iterators,
/// but dereferencing one-past-the-end is UB. This function uses the Accessed
/// parameter to distinguish: Accessed=true uses strict bounds (index < size),
/// Accessed=false allows one-past-the-end (index <= size).
///
/// Code that intentionally dereferences out-of-bounds (UB) may break with
/// optimizations. Disabled when -fsanitize=array-bounds is active.
///
void CodeGenFunction::EmitArrayBoundsConstraints(const ArraySubscriptExpr *E,
llvm::Value *IndexVal,
bool Accessed) {
// Disable with -fno-assume-array-bounds.
if (!CGM.getCodeGenOpts().AssumeArrayBounds)
return;

// Disable at -O0.
if (CGM.getCodeGenOpts().OptimizationLevel == 0)
return;

// Disable with array-bounds sanitizer.
if (SanOpts.has(SanitizerKind::ArrayBounds))
return;

// Use the provided IndexVal to avoid duplicating side effects.
// The caller has already emitted the index expression once.
if (!IndexVal)
return;

const Expr *Base = E->getBase();
const Expr *Idx = E->getIdx();
QualType BaseType = Base->getType();

if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Base)) {
if (ICE->getCastKind() == CK_ArrayToPointerDecay) {
BaseType = ICE->getSubExpr()->getType();
}
}

// Handle both constant arrays and VLAs (variable-length arrays.)
const ConstantArrayType *CAT = getContext().getAsConstantArrayType(BaseType);
llvm::Value *VLASize = nullptr;

if (!CAT) {
if (const VariableArrayType *VAT =
getContext().getAsVariableArrayType(BaseType))
VLASize = getVLASize(VAT).NumElts;
else
return; // Not a constant or VLA.
}

llvm::APInt ArraySize;
if (CAT)
ArraySize = CAT->getSize();

// Don't generate assumes for flexible array member pattern.
// Size-1 arrays: "struct { int len; char data[1]; }" (pre-C99 idiom.)
// Zero-length arrays: "struct { int len; char data[0]; }" (GCC extension
// https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)
// Both patterns use arrays as placeholders for variable-length data.
if (CAT && (ArraySize == 0 || ArraySize == 1)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should respect StrictFlexArraysLevel, probably.

if (const auto *ME = dyn_cast<MemberExpr>(Base->IgnoreParenImpCasts())) {
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
const RecordDecl *RD = FD->getParent();
// Check if this field is the last field in the record.
// Only the last field can be a flexible array member.
const FieldDecl *LastField = nullptr;
for (const auto *Field : RD->fields())
LastField = Field;
if (LastField == FD)
// This is a zero-length or size-1 array as the last field.
// Likely a flexible array member pattern - skip assumes.
return;
}
}
}

QualType IdxType = Idx->getType();
llvm::Type *IndexType = ConvertType(IdxType);
llvm::Value *ArraySizeVal;

if (CAT)
// Constant array: use compile-time size.
ArraySizeVal =
llvm::ConstantInt::get(IndexType, ArraySize.getLimitedValue());
else
// VLA: use runtime size.
ArraySizeVal =
VLASize->getType() == IndexType
? VLASize
: Builder.CreateIntCast(VLASize, IndexType, false, "vla.size.cast");

// Ensure index value has the same type as our constants.
if (IndexVal->getType() != IndexType) {
bool IsSigned = IdxType->isSignedIntegerOrEnumerationType();
IndexVal = Builder.CreateIntCast(IndexVal, IndexType, IsSigned, "idx.cast");
}

// Create bounds constraint: 0 <= index && index (< or <=) size.
// The Accessed parameter indicates whether the array element will be
// dereferenced. Per C/C++ standards, &arr[size] (one-past-the-end) is legal
// for iterators.
// Accessed = true: element is dereferenced, strict bounds: 0 <= index < size
// Accessed = false: address only, allow one-past-the-end: 0 <= index <= size

if (IdxType->isSignedIntegerOrEnumerationType()) {
// For signed indices: index >= 0 && index [<|<=] size.
llvm::Value *Zero = llvm::ConstantInt::get(IndexType, 0);
llvm::Value *LowerBound =
Builder.CreateICmpSGE(IndexVal, Zero, "idx.ge.zero");
llvm::Value *UpperBound = Accessed
? Builder.CreateICmpSLT(IndexVal, ArraySizeVal, "idx.slt.size")
: Builder.CreateICmpSLE(IndexVal, ArraySizeVal, "idx.sle.size");
llvm::Value *BoundsConstraint =
Builder.CreateAnd(LowerBound, UpperBound, "bounds.constraint");
Builder.CreateAssumption(BoundsConstraint);
} else {
// For unsigned indices: index [<|<=] size. (>= 0 is implicit.)
llvm::Value *UpperBound = Accessed
? Builder.CreateICmpULT(IndexVal, ArraySizeVal, "idx.ult.size")
: Builder.CreateICmpULE(IndexVal, ArraySizeVal, "idx.ule.size");
Builder.CreateAssumption(UpperBound);
}
}

LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
bool Accessed) {
// The index must always be an integer, which is not an aggregate. Emit it
Expand Down Expand Up @@ -4588,13 +4739,20 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
};
IdxPre = nullptr;

// Array bounds constraints will be emitted after index evaluation to avoid
// duplicating side effects from the index expression.

// If the base is a vector type, then we are forming a vector element lvalue
// with this subscript.
if (E->getBase()->getType()->isSubscriptableVectorType() &&
!isa<ExtVectorElementExpr>(E->getBase())) {
// Emit the vector as an lvalue to get its address.
LValue LHS = EmitLValue(E->getBase());
auto *Idx = EmitIdxAfterBase(/*Promote*/false);

// Emit array bounds constraints for vector subscripts.
EmitArrayBoundsConstraints(E, Idx, Accessed);

assert(LHS.isSimple() && "Can only subscript lvalue vectors here!");
return LValue::MakeVectorElt(LHS.getAddress(), Idx, E->getBase()->getType(),
LHS.getBaseInfo(), TBAAAccessInfo());
Expand Down Expand Up @@ -4635,6 +4793,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);

// Emit array bounds constraints for VLA access.
EmitArrayBoundsConstraints(E, Idx, Accessed);

// The element count here is the total number of non-VLA elements.
llvm::Value *numElements = getVLASize(vla).NumElts;

Expand All @@ -4659,6 +4820,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);

// Emit array bounds constraints for ObjC interface access.
EmitArrayBoundsConstraints(E, Idx, Accessed);

CharUnits InterfaceSize = getContext().getTypeSizeInChars(OIT);
llvm::Value *InterfaceSizeVal =
llvm::ConstantInt::get(Idx->getType(), InterfaceSize.getQuantity());
Expand Down Expand Up @@ -4694,6 +4858,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
ArrayLV = EmitLValue(Array);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);

// Emit array bounds constraints for optimization.
EmitArrayBoundsConstraints(E, Idx, Accessed);

if (SanOpts.has(SanitizerKind::ArrayBounds))
EmitCountedByBoundsChecking(Array, Idx, ArrayLV.getAddress(),
E->getIdx()->getType(), Array->getType(),
Expand Down Expand Up @@ -4737,6 +4904,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
Address BaseAddr =
EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);

// Emit array bounds constraints for pointer-based array access.
EmitArrayBoundsConstraints(E, Idx, Accessed);

QualType ptrType = E->getBase()->getType();
Addr = emitArraySubscriptGEP(*this, BaseAddr, Idx, E->getType(),
!getLangOpts().PointerOverflowDefined,
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2100,6 +2100,9 @@ Value *ScalarExprEmitter::VisitArraySubscriptExpr(ArraySubscriptExpr *E) {
if (CGF.SanOpts.has(SanitizerKind::ArrayBounds))
CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, /*Accessed*/true);

// Emit array bounds constraints for vector element access.
CGF.EmitArrayBoundsConstraints(E, Idx, /*Accessed=*/true);

return Builder.CreateExtractElement(Base, Idx, "vecext");
}

Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -3341,6 +3341,15 @@ class CodeGenFunction : public CodeGenTypeCache {
llvm::Value *Index, QualType IndexType,
QualType IndexedType, bool Accessed);

/// Emit array bounds constraints using llvm.assume for optimization hints.
/// Emits assume statements for array bounds without duplicating side effects.
/// Takes the already-emitted index value to avoid re-evaluating expressions
/// with side effects. The Accessed parameter distinguishes:
/// - dereferenced use strict bounds: index < size vs.
/// - address-only allows one-past: index <= size.
void EmitArrayBoundsConstraints(const ArraySubscriptExpr *E,
llvm::Value *IndexVal, bool Accessed);

/// Returns debug info, with additional annotation if
/// CGM.getCodeGenOpts().SanitizeAnnotateDebugInfo[Ordinal] is enabled for
/// any of the ordinals.
Expand Down
107 changes: 107 additions & 0 deletions clang/test/CodeGen/array-bounds-constraints-safety.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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_zero_length_array
struct ZeroLengthData {
int count;
int items[0]; // GNU C extension: zero-length array
};

int test_zero_length_array(struct ZeroLengthData *d, int i) {
// CHECK-NOT: call void @llvm.assume
// Zero-length array as last field should not generate bounds constraints.
// See https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
return d->items[i];
}

// 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
void init_vla(int *arr, int n);

int test_vla(int n, int i) {
int arr[n]; // Variable-length array.
init_vla(arr, n); // Initialize to avoid UB.
// CHECK: call void @llvm.assume
// For VLAs, generate bounds constraints using the runtime size: 0 <= i < n.
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 legal per C standard.
// Used in iterators (e.g., arr + size, std::end(arr)).
// No assumes are generated for address-only operations.
return &extern_array[100]; // Legal: one past the end.
Copy link
Member

Choose a reason for hiding this comment

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

I tried

extern int extern_array[100];
int *test_extern_array_val(int i) {
  return &extern_array[i];
}

with this PR an it generates

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

}

// CHECK-LABEL: define {{.*}} @test_extern_array
int test_extern_array(int i) {
// CHECK: call void @llvm.assume
// This will generate bounds constraints.
// The array is a constant-size global array.
// This is the safe case where we want optimization hints.
return extern_array[i];
}

// CHECK-LABEL: define {{.*}} @test_local_constant_array
void init_array(int *arr);
int test_local_constant_array(int i) {
int arr[10];
init_array(arr); // Initialize to avoid UB from uninitialized read.
// CHECK: call void @llvm.assume
// This will generate bounds constraints.
// We know the exact size of this alloca array.
// This is the safe case where we want optimization hints.
return arr[i];
}

// CHECK-LABEL: define {{.*}} @test_malloc_array
int *my_malloc(int);
int test_malloc_array(int i) {
// CHECK-NOT: call void @llvm.assume
// Dynamically allocated arrays accessed via pointers do not get bounds
// constraints.
int *x = my_malloc(100 * sizeof(int));
return x[i];
}
Loading
Loading