Skip to content

Commit 30b169b

Browse files
[llvm] Use computeConstantRange to improve llvm.objectsize computation
Using computeConstantRange, it is possible to compute valuable information for allocation functions, GEP and alloca, even in the presence of some 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 22c9145 commit 30b169b

File tree

6 files changed

+213
-11
lines changed

6 files changed

+213
-11
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: 98 additions & 3 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.
@@ -714,14 +724,54 @@ OffsetSpan ObjectSizeOffsetVisitor::computeImpl(Value *V) {
714724
// readjust the APInt as we pass it upwards in order for the APInt to match
715725
// the type the caller passed in.
716726
APInt Offset(InitialIntTyBits, 0);
727+
717728
V = V->stripAndAccumulateConstantOffsets(
718729
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true);
719730

731+
// Give it another try with approximated analysis. We don't start with this
732+
// one because stripAndAccumulateConstantOffsets behaves differently wrt.
733+
// overflows if we provide an external Analysis.
734+
if(isa<GetElementPtrInst>(V)) {
735+
// External Analysis used to compute the Min/Max value of individual Offsets
736+
// within a GEP.
737+
auto OffsetRangeAnalysis = [this, V](Value &VOffset, APInt &Offset) {
738+
if (auto *C = dyn_cast<ConstantInt>(&VOffset)) {
739+
Offset = C->getValue();
740+
return true;
741+
}
742+
if (Options.EvalMode != ObjectSizeOpts::Mode::Min &&
743+
Options.EvalMode != ObjectSizeOpts::Mode::Max) {
744+
return false;
745+
}
746+
ConstantRange CR = computeConstantRange(
747+
&VOffset, /*ForSigned*/ true, /*UseInstrInfo*/ true, /*AC=*/nullptr,
748+
/*CtxtI=*/dyn_cast<Instruction>(V), /*DT=*/Options.DT);
749+
if (CR.isFullSet())
750+
return false;
751+
752+
if (Options.EvalMode == ObjectSizeOpts::Mode::Min) {
753+
Offset = CR.getSignedMax();
754+
// Upper bound actually unknown.
755+
if (Offset.isMaxSignedValue())
756+
return false;
757+
} else {
758+
Offset = CR.getSignedMin();
759+
// Lower bound actually unknown.
760+
if (Offset.isMinSignedValue())
761+
return false;
762+
}
763+
return true;
764+
};
765+
766+
V = V->stripAndAccumulateConstantOffsets(
767+
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true,
768+
/*ExternalAnalysis=*/OffsetRangeAnalysis);
769+
}
770+
720771
// Later we use the index type size and zero but it will match the type of the
721772
// value that is passed to computeImpl.
722773
IntTyBits = DL.getIndexTypeSizeInBits(V->getType());
723774
Zero = APInt::getZero(IntTyBits);
724-
725775
OffsetSpan ORT = computeValue(V);
726776

727777
bool IndexTypeSizeChanged = InitialIntTyBits != IntTyBits;
@@ -801,6 +851,26 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
801851

802852
return Overflow ? ObjectSizeOffsetVisitor::unknown()
803853
: OffsetSpan(Zero, alignAndCap(Size, I.getAlign()));
854+
} else {
855+
ConstantRange CR =
856+
computeConstantRange(ArraySize, /*ForSigned*/ false,
857+
/*UseInstrInfo*/ true, /*AC=*/nullptr,
858+
/*CtxtI=*/&I, /*DT=*/Options.DT);
859+
if (CR.isFullSet())
860+
return ObjectSizeOffsetVisitor::unknown();
861+
APInt Bound;
862+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
863+
Bound = CR.getUnsignedMax();
864+
// Upper bound actually unknown.
865+
if (Bound.isMaxValue())
866+
return ObjectSizeOffsetVisitor::unknown();
867+
} else {
868+
Bound = CR.getUnsignedMin();
869+
// Lower bound actually unknown.
870+
if (Bound.isMinValue())
871+
return ObjectSizeOffsetVisitor::unknown();
872+
}
873+
return OffsetSpan(Zero, alignAndCap(Bound, I.getAlign()));
804874
}
805875
return ObjectSizeOffsetVisitor::unknown();
806876
}
@@ -818,7 +888,32 @@ OffsetSpan ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
818888
}
819889

820890
OffsetSpan ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
821-
if (std::optional<APInt> Size = getAllocSize(&CB, TLI)) {
891+
auto Mapper = [&CB, this](const Value *V) -> const Value * {
892+
if (!V->getType()->isIntegerTy())
893+
return V;
894+
if (isa<ConstantInt>(V))
895+
return V;
896+
ConstantRange CR = computeConstantRange(
897+
V, /*ForSigned*/ false, /*UseInstrInfo*/ true, /*AC=*/nullptr,
898+
/*CtxtI=*/&CB, /*DT=*/Options.DT);
899+
if (CR.isFullSet())
900+
return V;
901+
902+
APInt Bound;
903+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
904+
Bound = CR.getUnsignedMax();
905+
// Upper bound actually unknown.
906+
if (Bound.isMaxValue())
907+
return V;
908+
} else {
909+
Bound = CR.getUnsignedMin();
910+
// Lower bound actually unknown.
911+
if (Bound.isMinValue())
912+
return V;
913+
}
914+
return ConstantInt::get(V->getType(), Bound);
915+
};
916+
if (std::optional<APInt> Size = getAllocSize(&CB, TLI, Mapper)) {
822917
// Very large unsigned value cannot be represented as OffsetSpan.
823918
if (Size->isNegative())
824919
return ObjectSizeOffsetVisitor::unknown();

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)