Skip to content

Commit 1dcb3db

Browse files
[llvm] Fix behavior of llvm.objectsize in presence of negative / large offset (#115504)
The internal structure used to carry intermediate computations hold signed values. If an object size happens to overflow signed values, we can get invalid result, so make sure this situation never happens. This is not very limitative as static allocation of such large values should scarcely happen.
1 parent b4c0ef1 commit 1dcb3db

File tree

4 files changed

+168
-9
lines changed

4 files changed

+168
-9
lines changed

llvm/include/llvm/Analysis/MemoryBuiltins.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ struct SizeOffsetAPInt : public SizeOffsetType<APInt, SizeOffsetAPInt> {
223223

224224
/// OffsetSpan - Used internally by \p ObjectSizeOffsetVisitor. Represents a
225225
/// point in memory as a pair of allocated bytes before and after it.
226+
///
227+
/// \c Before and \c After fields are signed values. It makes it possible to
228+
/// represent out-of-bound access, e.g. as a result of a GEP, at the expense of
229+
/// not being able to represent very large allocation.
226230
struct OffsetSpan {
227231
APInt Before; /// Number of allocated bytes before this point.
228232
APInt After; /// Number of allocated bytes after this point.

llvm/lib/Analysis/MemoryBuiltins.cpp

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,13 @@ Value *llvm::getFreedOperand(const CallBase *CB, const TargetLibraryInfo *TLI) {
564564
static APInt getSizeWithOverflow(const SizeOffsetAPInt &Data) {
565565
APInt Size = Data.Size;
566566
APInt Offset = Data.Offset;
567-
if (Offset.isNegative() || Size.ult(Offset))
568-
return APInt(Size.getBitWidth(), 0);
567+
568+
assert(!Offset.isNegative() &&
569+
"size for a pointer before the allocated object is ambiguous");
570+
571+
if (Size.ult(Offset))
572+
return APInt::getZero(Size.getBitWidth());
573+
569574
return Size - Offset;
570575
}
571576

@@ -668,10 +673,14 @@ STATISTIC(ObjectVisitorArgument,
668673
STATISTIC(ObjectVisitorLoad,
669674
"Number of load instructions with unsolved size and offset");
670675

676+
/// Align \p Size according to \p Alignment. If \p Size is greater than
677+
/// getSignedMaxValue(), set it as unknown as we can only represent signed value
678+
/// in OffsetSpan.
671679
APInt ObjectSizeOffsetVisitor::align(APInt Size, MaybeAlign Alignment) {
672680
if (Options.RoundToAlign && Alignment)
673-
return APInt(IntTyBits, alignTo(Size.getZExtValue(), *Alignment));
674-
return Size;
681+
Size = APInt(IntTyBits, alignTo(Size.getZExtValue(), *Alignment));
682+
683+
return Size.isNegative() ? APInt() : Size;
675684
}
676685

677686
ObjectSizeOffsetVisitor::ObjectSizeOffsetVisitor(const DataLayout &DL,
@@ -733,8 +742,26 @@ OffsetSpan ObjectSizeOffsetVisitor::computeImpl(Value *V) {
733742
ORT.After = APInt();
734743
}
735744
// If the computed bound is "unknown" we cannot add the stripped offset.
736-
return {(ORT.knownBefore() ? ORT.Before + Offset : ORT.Before),
737-
(ORT.knownAfter() ? ORT.After - Offset : ORT.After)};
745+
if (ORT.knownBefore()) {
746+
bool Overflow;
747+
ORT.Before = ORT.Before.sadd_ov(Offset, Overflow);
748+
if (Overflow)
749+
ORT.Before = APInt();
750+
}
751+
if (ORT.knownAfter()) {
752+
bool Overflow;
753+
ORT.After = ORT.After.ssub_ov(Offset, Overflow);
754+
if (Overflow)
755+
ORT.After = APInt();
756+
}
757+
758+
// We end up pointing on a location that's outside of the original object.
759+
// This is UB, and we'd rather return an empty location then.
760+
if (ORT.knownBefore() && ORT.Before.isNegative()) {
761+
ORT.Before = APInt::getZero(ORT.Before.getBitWidth());
762+
ORT.After = APInt::getZero(ORT.Before.getBitWidth());
763+
}
764+
return ORT;
738765
}
739766

740767
OffsetSpan ObjectSizeOffsetVisitor::computeValue(Value *V) {
@@ -780,6 +807,7 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
780807
if (!isUIntN(IntTyBits, ElemSize.getKnownMinValue()))
781808
return ObjectSizeOffsetVisitor::unknown();
782809
APInt Size(IntTyBits, ElemSize.getKnownMinValue());
810+
783811
if (!I.isArrayAllocation())
784812
return OffsetSpan(Zero, align(Size, I.getAlign()));
785813

@@ -791,6 +819,7 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
791819

792820
bool Overflow;
793821
Size = Size.umul_ov(NumElems, Overflow);
822+
794823
return Overflow ? ObjectSizeOffsetVisitor::unknown()
795824
: OffsetSpan(Zero, align(Size, I.getAlign()));
796825
}
@@ -810,8 +839,12 @@ OffsetSpan ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
810839
}
811840

812841
OffsetSpan ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
813-
if (std::optional<APInt> Size = getAllocSize(&CB, TLI))
842+
if (std::optional<APInt> Size = getAllocSize(&CB, TLI)) {
843+
// Very large unsigned value cannot be represented as OffsetSpan.
844+
if (Size->isNegative())
845+
return ObjectSizeOffsetVisitor::unknown();
814846
return OffsetSpan(Zero, *Size);
847+
}
815848
return ObjectSizeOffsetVisitor::unknown();
816849
}
817850

@@ -944,7 +977,11 @@ OffsetSpan ObjectSizeOffsetVisitor::findLoadOffsetRange(
944977
if (!C)
945978
return Unknown();
946979

947-
return Known({APInt(C->getValue().getBitWidth(), 0), C->getValue()});
980+
APInt CSize = C->getValue();
981+
if (CSize.isNegative())
982+
return Unknown();
983+
984+
return Known({APInt(CSize.getBitWidth(), 0), CSize});
948985
}
949986

950987
return Unknown();

llvm/test/Transforms/LowerConstantIntrinsics/builtin-object-size-phi.ll

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,54 @@ if.end:
118118
ret i64 %size
119119
}
120120

121+
define dso_local i64 @pick_max_large(i1 %c) local_unnamed_addr {
122+
; CHECK-LABEL: @pick_max_large(
123+
; CHECK-NEXT: [[BUFFER:%.*]] = alloca i8, i64 -7, align 1
124+
; CHECK-NEXT: [[S:%.*]] = select i1 [[C:%.*]], ptr null, ptr [[BUFFER]]
125+
; CHECK-NEXT: ret i64 -1
126+
;
127+
%buffer = alloca i8, i64 -7 ; Actually a very large positive integer
128+
%s = select i1 %c, ptr null, ptr %buffer
129+
%objsize = tail call i64 @llvm.objectsize.i64.p0(ptr %s, i1 false, i1 false, i1 false)
130+
ret i64 %objsize
131+
132+
}
133+
134+
define dso_local i64 @pick_max_one_oob(i1 %c0, i1 %c1) {
135+
; CHECK-LABEL: @pick_max_one_oob(
136+
; CHECK-NEXT: [[P:%.*]] = alloca [2 x i8], align 1
137+
; CHECK-NEXT: br i1 [[C0:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
138+
; CHECK: if.then:
139+
; CHECK-NEXT: [[P_THEN:%.*]] = getelementptr inbounds [2 x i8], ptr [[P]], i64 0, i64 1
140+
; CHECK-NEXT: br label [[IF_END:%.*]]
141+
; CHECK: if.else:
142+
; CHECK-NEXT: [[P_ELSE:%.*]] = getelementptr inbounds [2 x i8], ptr [[P]], i64 0, i64 -1
143+
; CHECK-NEXT: br label [[IF_END]]
144+
; CHECK: if.end:
145+
; CHECK-NEXT: [[P_END:%.*]] = phi ptr [ [[P_ELSE]], [[IF_ELSE]] ], [ [[P_THEN]], [[IF_THEN]] ]
146+
; CHECK-NEXT: [[OBJSIZE:%.*]] = select i1 [[C1:%.*]], i64 1, i64 0
147+
; CHECK-NEXT: ret i64 [[OBJSIZE]]
148+
;
149+
%p = alloca [2 x i8], align 1
150+
br i1 %c0, label %if.then, label %if.else
151+
152+
if.then:
153+
%p.then = getelementptr inbounds [2 x i8], ptr %p, i64 0, i64 1
154+
br label %if.end
155+
156+
if.else:
157+
%p.else = getelementptr inbounds [2 x i8], ptr %p, i64 0, i64 -1
158+
br label %if.end
159+
160+
if.end:
161+
%p.end = phi ptr [%p.else, %if.else], [%p.then, %if.then]
162+
%objsize.max = call i64 @llvm.objectsize.i64.p0(ptr %p.end, i1 false, i1 true, i1 false)
163+
%objsize.min = call i64 @llvm.objectsize.i64.p0(ptr %p.end, i1 true, i1 true, i1 false)
164+
%objsize = select i1 %c1, i64 %objsize.max, i64 %objsize.min
165+
ret i64 %objsize
166+
}
167+
168+
121169
define i64 @pick_negative_offset(i32 %n) {
122170
; CHECK-LABEL: @pick_negative_offset(
123171
; CHECK-NEXT: entry:

llvm/test/Transforms/LowerConstantIntrinsics/objectsize_basic.ll

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,77 @@ define i64 @out_of_bound_gep() {
195195
ret i64 %objsize
196196
}
197197

198-
define i64 @out_of_bound_negative_gep() {
198+
define i64 @wrapping_gep(i1 %c) {
199+
; CHECK-LABEL: @wrapping_gep(
200+
; CHECK-NEXT: [[OBJ:%.*]] = alloca i8, i64 4, align 1
201+
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i64 -9223372036854775807
202+
; CHECK-NEXT: [[SLIDE_BIS:%.*]] = getelementptr i8, ptr [[SLIDE]], i64 -9223372036854775808
203+
; CHECK-NEXT: ret i64 3
204+
;
205+
%obj = alloca i8, i64 4
206+
%slide = getelementptr i8, ptr %obj, i64 9223372036854775809
207+
%slide.bis = getelementptr i8, ptr %slide, i64 9223372036854775808
208+
%objsize = call i64 @llvm.objectsize.i64(ptr %slide.bis, i1 false, i1 false, i1 false)
209+
ret i64 %objsize
210+
}
211+
212+
define i64 @wrapping_gep_neg(i1 %c) {
213+
; CHECK-LABEL: @wrapping_gep_neg(
214+
; CHECK-NEXT: [[OBJ:%.*]] = alloca i8, i64 4, align 1
215+
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i64 9223372036854775807
216+
; CHECK-NEXT: [[SLIDE_BIS:%.*]] = getelementptr i8, ptr [[SLIDE]], i64 9223372036854775807
217+
; CHECK-NEXT: ret i64 0
218+
;
219+
%obj = alloca i8, i64 4
220+
%slide = getelementptr i8, ptr %obj, i64 9223372036854775807
221+
%slide.bis = getelementptr i8, ptr %slide, i64 9223372036854775807
222+
%objsize = call i64 @llvm.objectsize.i64(ptr %slide.bis, i1 false, i1 false, i1 false)
223+
ret i64 %objsize
224+
}
225+
226+
define i64 @wrapping_gep_large_alloc(i1 %c) {
227+
; CHECK-LABEL: @wrapping_gep_large_alloc(
228+
; CHECK-NEXT: [[OBJ:%.*]] = alloca i8, i64 9223372036854775807, align 1
229+
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i64 9223372036854775807
230+
; CHECK-NEXT: [[SLIDE_BIS:%.*]] = getelementptr i8, ptr [[SLIDE]], i64 3
231+
; CHECK-NEXT: [[SLIDE_TER:%.*]] = getelementptr i8, ptr [[SLIDE_BIS]], i64 -4
232+
; CHECK-NEXT: ret i64 1
233+
;
234+
%obj = alloca i8, i64 9223372036854775807
235+
%slide = getelementptr i8, ptr %obj, i64 9223372036854775807
236+
%slide.bis = getelementptr i8, ptr %slide, i64 3
237+
%slide.ter = getelementptr i8, ptr %slide.bis, i64 -4
238+
%objsize = call i64 @llvm.objectsize.i64(ptr %slide.ter, i1 false, i1 false, i1 false)
239+
ret i64 %objsize
240+
}
241+
242+
; We don't analyze allocations larger than platform's ptrdiff_t
243+
define i64 @large_alloca() {
244+
; CHECK-LABEL: @large_alloca(
245+
; CHECK-NEXT: [[OBJ:%.*]] = alloca i8, i64 -9223372036854775808, align 1
246+
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i64 9223372036854775807
247+
; CHECK-NEXT: ret i64 -1
248+
;
249+
%obj = alloca i8, i64 9223372036854775808
250+
%slide = getelementptr i8, ptr %obj, i64 9223372036854775807
251+
%objsize = call i64 @llvm.objectsize.i64(ptr %slide, i1 false, i1 false, i1 false)
252+
ret i64 %objsize
253+
}
254+
255+
; We don't analyze allocations larger than platform's ptrdiff_t
256+
define i64 @large_malloc() {
257+
; CHECK-LABEL: @large_malloc(
258+
; CHECK-NEXT: [[OBJ:%.*]] = call ptr @malloc(i64 -9223372036854775808)
259+
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i64 9223372036854775807
260+
; CHECK-NEXT: ret i64 -1
261+
;
262+
%obj = call ptr @malloc(i64 9223372036854775808)
263+
%slide = getelementptr i8, ptr %obj, i64 9223372036854775807
264+
%objsize = call i64 @llvm.objectsize.i64(ptr %slide, i1 false, i1 false, i1 false)
265+
ret i64 %objsize
266+
}
267+
268+
define i64 @out_of_bound_negative_gep(i1 %c) {
199269
; CHECK-LABEL: @out_of_bound_negative_gep(
200270
; CHECK-NEXT: [[OBJ:%.*]] = alloca i8, i32 4, align 1
201271
; CHECK-NEXT: [[SLIDE:%.*]] = getelementptr i8, ptr [[OBJ]], i8 -8

0 commit comments

Comments
 (0)