Skip to content

Commit 5f34281

Browse files
[llvm] Use computeConstantRange to improve llvm.objectsize computation (llvm#114673)
Using LazyValueInfo, it is possible to compute valuable information for allocation functions, GEP and alloca, even in the presence of dynamic information. llvm.objectsize plays an important role in _FORTIFY_SOURCE definitions, so improving its diagnostic in turns improves the security of compiled application. As a side note, as a result of recent optimization improvements, clang no longer passes https://github.com/serge-sans-paille/builtin_object_size-test-suite This commit restores the situation and greatly improves the scope of code handled by the static version of __builtin_object_size.
1 parent 9b909b8 commit 5f34281

File tree

6 files changed

+206
-12
lines changed

6 files changed

+206
-12
lines changed

llvm/include/llvm/Analysis/MemoryBuiltins.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class AAResults;
3232
class Argument;
3333
class ConstantPointerNull;
3434
class DataLayout;
35+
class DominatorTree;
3536
class ExtractElementInst;
3637
class ExtractValueInst;
3738
class GEPOperator;
@@ -160,8 +161,10 @@ struct ObjectSizeOpts {
160161
/// though they can't be evaluated. Otherwise, null is always considered to
161162
/// point to a 0 byte region of memory.
162163
bool NullIsUnknownSize = false;
163-
/// If set, used for more accurate evaluation
164+
/// If set, used for more accurate evaluation.
164165
AAResults *AA = nullptr;
166+
/// If set, used for more accurate evaluation.
167+
DominatorTree *DT = nullptr;
165168
};
166169

167170
/// Compute the size of the object pointed by Ptr. Returns true and the
@@ -186,6 +189,12 @@ Value *lowerObjectSizeCall(
186189
const TargetLibraryInfo *TLI, AAResults *AA, bool MustSucceed,
187190
SmallVectorImpl<Instruction *> *InsertedInstructions = nullptr);
188191

192+
Value *lowerObjectSizeCall(
193+
IntrinsicInst *ObjectSize, const DataLayout &DL,
194+
const TargetLibraryInfo *TLI, AAResults *AA, DominatorTree *DT,
195+
bool MustSucceed,
196+
SmallVectorImpl<Instruction *> *InsertedInstructions = nullptr);
197+
189198
/// SizeOffsetType - A base template class for the object size visitors. Used
190199
/// here as a self-documenting way to handle the values rather than using a
191200
/// \p std::pair.

llvm/include/llvm/IR/Value.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -723,12 +723,16 @@ class Value {
723723
bool AllowInvariantGroup = false,
724724
function_ref<bool(Value &Value, APInt &Offset)> ExternalAnalysis =
725725
nullptr) const;
726-
Value *stripAndAccumulateConstantOffsets(const DataLayout &DL, APInt &Offset,
727-
bool AllowNonInbounds,
728-
bool AllowInvariantGroup = false) {
726+
727+
Value *stripAndAccumulateConstantOffsets(
728+
const DataLayout &DL, APInt &Offset, bool AllowNonInbounds,
729+
bool AllowInvariantGroup = false,
730+
function_ref<bool(Value &Value, APInt &Offset)> ExternalAnalysis =
731+
nullptr) {
729732
return const_cast<Value *>(
730733
static_cast<const Value *>(this)->stripAndAccumulateConstantOffsets(
731-
DL, Offset, AllowNonInbounds, AllowInvariantGroup));
734+
DL, Offset, AllowNonInbounds, AllowInvariantGroup,
735+
ExternalAnalysis));
732736
}
733737

734738
/// This is a wrapper around stripAndAccumulateConstantOffsets with the

llvm/lib/Analysis/MemoryBuiltins.cpp

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "llvm/IR/Constants.h"
2626
#include "llvm/IR/DataLayout.h"
2727
#include "llvm/IR/DerivedTypes.h"
28+
#include "llvm/IR/Dominators.h"
2829
#include "llvm/IR/Function.h"
2930
#include "llvm/IR/GlobalAlias.h"
3031
#include "llvm/IR/GlobalVariable.h"
@@ -589,19 +590,28 @@ Value *llvm::lowerObjectSizeCall(IntrinsicInst *ObjectSize,
589590
const TargetLibraryInfo *TLI,
590591
bool MustSucceed) {
591592
return lowerObjectSizeCall(ObjectSize, DL, TLI, /*AAResults=*/nullptr,
592-
MustSucceed);
593+
/*DT=*/nullptr, MustSucceed);
593594
}
594595

595596
Value *llvm::lowerObjectSizeCall(
596597
IntrinsicInst *ObjectSize, const DataLayout &DL,
597598
const TargetLibraryInfo *TLI, AAResults *AA, bool MustSucceed,
598599
SmallVectorImpl<Instruction *> *InsertedInstructions) {
600+
return lowerObjectSizeCall(ObjectSize, DL, TLI, AA, /*DT=*/nullptr,
601+
MustSucceed, InsertedInstructions);
602+
}
603+
604+
Value *llvm::lowerObjectSizeCall(
605+
IntrinsicInst *ObjectSize, const DataLayout &DL,
606+
const TargetLibraryInfo *TLI, AAResults *AA, DominatorTree *DT,
607+
bool MustSucceed, SmallVectorImpl<Instruction *> *InsertedInstructions) {
599608
assert(ObjectSize->getIntrinsicID() == Intrinsic::objectsize &&
600609
"ObjectSize must be a call to llvm.objectsize!");
601610

602611
bool MaxVal = cast<ConstantInt>(ObjectSize->getArgOperand(1))->isZero();
603612
ObjectSizeOpts EvalOptions;
604613
EvalOptions.AA = AA;
614+
EvalOptions.DT = DT;
605615

606616
// Unless we have to fold this to something, try to be as accurate as
607617
// possible.
@@ -708,14 +718,46 @@ OffsetSpan ObjectSizeOffsetVisitor::computeImpl(Value *V) {
708718
// readjust the APInt as we pass it upwards in order for the APInt to match
709719
// the type the caller passed in.
710720
APInt Offset(InitialIntTyBits, 0);
721+
722+
// External Analysis used to compute the Min/Max value of individual Offsets
723+
// within a GEP.
724+
auto OffsetRangeAnalysis = [this, V](Value &VOffset, APInt &Offset) {
725+
if (auto *C = dyn_cast<ConstantInt>(&VOffset)) {
726+
Offset = C->getValue();
727+
return true;
728+
}
729+
if (Options.EvalMode != ObjectSizeOpts::Mode::Min &&
730+
Options.EvalMode != ObjectSizeOpts::Mode::Max) {
731+
return false;
732+
}
733+
ConstantRange CR = computeConstantRange(
734+
&VOffset, /*ForSigned*/ true, /*UseInstrInfo*/ true, /*AC=*/nullptr,
735+
/*CtxtI=*/dyn_cast<Instruction>(V), /*DT=*/Options.DT);
736+
if (CR.isFullSet())
737+
return false;
738+
739+
if (Options.EvalMode == ObjectSizeOpts::Mode::Min) {
740+
Offset = CR.getSignedMax();
741+
// Upper bound actually unknown.
742+
if (Offset.isMaxSignedValue())
743+
return false;
744+
} else {
745+
Offset = CR.getSignedMin();
746+
// Lower bound actually unknown.
747+
if (Offset.isMinSignedValue())
748+
return false;
749+
}
750+
return true;
751+
};
752+
711753
V = V->stripAndAccumulateConstantOffsets(
712-
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true);
754+
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true,
755+
/*ExternalAnalysis=*/OffsetRangeAnalysis);
713756

714757
// Later we use the index type size and zero but it will match the type of the
715758
// value that is passed to computeImpl.
716759
IntTyBits = DL.getIndexTypeSizeInBits(V->getType());
717760
Zero = APInt::getZero(IntTyBits);
718-
719761
OffsetSpan ORT = computeValue(V);
720762

721763
bool IndexTypeSizeChanged = InitialIntTyBits != IntTyBits;
@@ -793,6 +835,26 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
793835
Size = Size.umul_ov(NumElems, Overflow);
794836
return Overflow ? ObjectSizeOffsetVisitor::unknown()
795837
: OffsetSpan(Zero, align(Size, I.getAlign()));
838+
} else {
839+
ConstantRange CR =
840+
computeConstantRange(ArraySize, /*ForSigned*/ false,
841+
/*UseInstrInfo*/ true, /*AC=*/nullptr,
842+
/*CtxtI=*/&I, /*DT=*/Options.DT);
843+
if (CR.isFullSet())
844+
return ObjectSizeOffsetVisitor::unknown();
845+
APInt Bound;
846+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
847+
Bound = CR.getUnsignedMax();
848+
// Upper bound actually unknown.
849+
if (Bound.isMaxValue())
850+
return ObjectSizeOffsetVisitor::unknown();
851+
} else {
852+
Bound = CR.getUnsignedMin();
853+
// Lower bound actually unknown.
854+
if (Bound.isMinValue())
855+
return ObjectSizeOffsetVisitor::unknown();
856+
}
857+
return OffsetSpan(Zero, align(Bound, I.getAlign()));
796858
}
797859
return ObjectSizeOffsetVisitor::unknown();
798860
}
@@ -810,7 +872,32 @@ OffsetSpan ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
810872
}
811873

812874
OffsetSpan ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
813-
if (std::optional<APInt> Size = getAllocSize(&CB, TLI))
875+
if (std::optional<APInt> Size =
876+
getAllocSize(&CB, TLI, [&CB, this](const Value *V) -> const Value * {
877+
if (!V->getType()->isIntegerTy())
878+
return V;
879+
if (isa<ConstantInt>(V))
880+
return V;
881+
ConstantRange CR = computeConstantRange(
882+
V, /*ForSigned*/ false, /*UseInstrInfo*/ true, /*AC=*/nullptr,
883+
/*CtxtI=*/&CB, /*DT=*/Options.DT);
884+
if (CR.isFullSet())
885+
return V;
886+
887+
APInt Bound;
888+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
889+
Bound = CR.getUnsignedMax();
890+
// Upper bound actually unknown.
891+
if (Bound.isMaxValue())
892+
return V;
893+
} else {
894+
Bound = CR.getUnsignedMin();
895+
// Lower bound actually unknown.
896+
if (Bound.isMinValue())
897+
return V;
898+
}
899+
return ConstantInt::get(V->getType(), Bound);
900+
}))
814901
return OffsetSpan(Zero, *Size);
815902
return ObjectSizeOffsetVisitor::unknown();
816903
}

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3318,8 +3318,9 @@ Instruction *InstCombinerImpl::visitAllocSite(Instruction &MI) {
33183318
if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
33193319
if (II->getIntrinsicID() == Intrinsic::objectsize) {
33203320
SmallVector<Instruction *> InsertedInstructions;
3321-
Value *Result = lowerObjectSizeCall(
3322-
II, DL, &TLI, AA, /*MustSucceed=*/true, &InsertedInstructions);
3321+
Value *Result =
3322+
lowerObjectSizeCall(II, DL, &TLI, AA, &DT,
3323+
/*MustSucceed=*/true, &InsertedInstructions);
33233324
for (Instruction *Inserted : InsertedInstructions)
33243325
Worklist.add(Inserted);
33253326
replaceInstUsesWith(*I, Result);

llvm/lib/Transforms/Scalar/LowerConstantIntrinsics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ bool llvm::lowerConstantIntrinsics(Function &F, const TargetLibraryInfo &TLI,
143143
IsConstantIntrinsicsHandled++;
144144
break;
145145
case Intrinsic::objectsize:
146-
NewValue = lowerObjectSizeCall(II, DL, &TLI, true);
146+
NewValue = lowerObjectSizeCall(II, DL, &TLI, /*AA=*/nullptr, DT, true);
147147
LLVM_DEBUG(dbgs() << "Folding " << *II << " to " << *NewValue << "\n");
148148
ObjectSizeIntrinsicsHandled++;
149149
break;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
2+
; RUN: opt -passes=lower-constant-intrinsics -S < %s | FileCheck %s
3+
4+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
5+
target triple = "x86_64-unknown-linux-gnu"
6+
7+
declare i64 @llvm.objectsize.i64.p0(ptr, i1 immarg, i1 immarg, i1 immarg)
8+
declare noalias ptr @malloc(i64 noundef) #0
9+
10+
define i64 @select_alloc_size(i1 %cond) {
11+
; CHECK-LABEL: @select_alloc_size(
12+
; CHECK-NEXT: [[SIZE:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
13+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 [[SIZE]], align 1
14+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 4, i64 3
15+
; CHECK-NEXT: ret i64 [[RES]]
16+
;
17+
%size = select i1 %cond, i64 3, i64 4
18+
%ptr = alloca i8, i64 %size
19+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 false, i1 true, i1 false)
20+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 true, i1 true, i1 false)
21+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
22+
ret i64 %res
23+
}
24+
25+
define i64 @select_malloc_size(i1 %cond) {
26+
; CHECK-LABEL: @select_malloc_size(
27+
; CHECK-NEXT: [[SIZE:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
28+
; CHECK-NEXT: [[PTR:%.*]] = call noalias ptr @malloc(i64 noundef [[SIZE]])
29+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 4, i64 3
30+
; CHECK-NEXT: ret i64 [[RES]]
31+
;
32+
%size = select i1 %cond, i64 3, i64 4
33+
%ptr = call noalias ptr @malloc(i64 noundef %size)
34+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 false, i1 true, i1 false)
35+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 true, i1 true, i1 false)
36+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
37+
ret i64 %res
38+
}
39+
40+
define i64 @select_gep_offset(i1 %cond) {
41+
; CHECK-LABEL: @select_gep_offset(
42+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 10, align 1
43+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
44+
; CHECK-NEXT: [[PTR_SLIDE:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 [[OFFSET]]
45+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 7, i64 6
46+
; CHECK-NEXT: ret i64 [[RES]]
47+
;
48+
%ptr = alloca i8, i64 10
49+
%offset = select i1 %cond, i64 3, i64 4
50+
%ptr.slide = getelementptr inbounds i8, ptr %ptr, i64 %offset
51+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 false, i1 true, i1 false)
52+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 true, i1 true, i1 false)
53+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
54+
ret i64 %res
55+
}
56+
57+
define i64 @select_gep_neg_offset(i1 %cond) {
58+
; CHECK-LABEL: @select_gep_neg_offset(
59+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 10, align 1
60+
; CHECK-NEXT: [[PTR_SLIDE_1:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 5
61+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i64 -3, i64 -4
62+
; CHECK-NEXT: [[PTR_SLIDE_2:%.*]] = getelementptr inbounds i8, ptr [[PTR_SLIDE_1]], i64 [[OFFSET]]
63+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 9, i64 8
64+
; CHECK-NEXT: ret i64 [[RES]]
65+
;
66+
%ptr = alloca i8, i64 10
67+
%ptr.slide.1 = getelementptr inbounds i8, ptr %ptr, i64 5
68+
%offset = select i1 %cond, i64 -3, i64 -4
69+
%ptr.slide.2 = getelementptr inbounds i8, ptr %ptr.slide.1, i64 %offset
70+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide.2, i1 false, i1 true, i1 false)
71+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide.2, i1 true, i1 true, i1 false)
72+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
73+
ret i64 %res
74+
}
75+
76+
define i64 @select_gep_offsets(i1 %cond) {
77+
; CHECK-LABEL: @select_gep_offsets(
78+
; CHECK-NEXT: [[PTR:%.*]] = alloca [10 x i8], i64 2, align 1
79+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i32 0, i32 1
80+
; CHECK-NEXT: [[PTR_SLIDE:%.*]] = getelementptr inbounds [10 x i8], ptr [[PTR]], i32 [[OFFSET]], i32 5
81+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 15, i64 5
82+
; CHECK-NEXT: ret i64 [[RES]]
83+
;
84+
%ptr = alloca [10 x i8], i64 2
85+
%offset = select i1 %cond, i32 0, i32 1
86+
%ptr.slide = getelementptr inbounds [10 x i8], ptr %ptr, i32 %offset, i32 5
87+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 false, i1 true, i1 false)
88+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 true, i1 true, i1 false)
89+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
90+
ret i64 %res
91+
}
92+
93+
attributes #0 = { nounwind allocsize(0) }

0 commit comments

Comments
 (0)