Skip to content

Commit 59dd36c

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 af6ebb7 commit 59dd36c

File tree

6 files changed

+241
-10
lines changed

6 files changed

+241
-10
lines changed

llvm/include/llvm/Analysis/MemoryBuiltins.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ namespace llvm {
2929

3030
class AllocaInst;
3131
class AAResults;
32+
class AssumptionCache;
3233
class Argument;
3334
class ConstantPointerNull;
3435
class DataLayout;
36+
class DominatorTree;
3537
class ExtractElementInst;
3638
class ExtractValueInst;
3739
class GEPOperator;
@@ -160,8 +162,12 @@ struct ObjectSizeOpts {
160162
/// though they can't be evaluated. Otherwise, null is always considered to
161163
/// point to a 0 byte region of memory.
162164
bool NullIsUnknownSize = false;
163-
/// If set, used for more accurate evaluation
165+
/// If set, used for more accurate evaluation.
164166
AAResults *AA = nullptr;
167+
/// If set, used for more accurate evaluation.
168+
AssumptionCache *AC = nullptr;
169+
/// If set, used for more accurate evaluation.
170+
DominatorTree *DT = nullptr;
165171
};
166172

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

195+
Value *lowerObjectSizeCall(
196+
IntrinsicInst *ObjectSize, const DataLayout &DL,
197+
const TargetLibraryInfo *TLI, AAResults *AA, DominatorTree *DT,
198+
AssumptionCache *AC, bool MustSucceed,
199+
SmallVectorImpl<Instruction *> *InsertedInstructions = nullptr);
200+
189201
/// SizeOffsetType - A base template class for the object size visitors. Used
190202
/// here as a self-documenting way to handle the values rather than using a
191203
/// \p std::pair.
@@ -275,6 +287,7 @@ class ObjectSizeOffsetVisitor
275287
OffsetSpan visitExtractValueInst(ExtractValueInst &I);
276288
OffsetSpan visitGlobalAlias(GlobalAlias &GA);
277289
OffsetSpan visitGlobalVariable(GlobalVariable &GV);
290+
OffsetSpan visitGetElementPtr(GetElementPtrInst &GEP);
278291
OffsetSpan visitIntToPtrInst(IntToPtrInst &);
279292
OffsetSpan visitLoadInst(LoadInst &I);
280293
OffsetSpan visitPHINode(PHINode &);

llvm/include/llvm/Transforms/Scalar/LowerConstantIntrinsics.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919

2020
namespace llvm {
2121

22+
class AssumptionCache;
2223
class DominatorTree;
2324
class Function;
2425
class TargetLibraryInfo;
2526

2627
bool lowerConstantIntrinsics(Function &F, const TargetLibraryInfo &TLI,
27-
DominatorTree *DT);
28+
DominatorTree *DT, AssumptionCache *AC = nullptr);
2829

2930
struct LowerConstantIntrinsicsPass :
3031
PassInfoMixin<LowerConstantIntrinsicsPass> {

llvm/lib/Analysis/MemoryBuiltins.cpp

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "llvm/ADT/STLExtras.h"
1717
#include "llvm/ADT/Statistic.h"
1818
#include "llvm/Analysis/AliasAnalysis.h"
19+
#include "llvm/Analysis/AssumptionCache.h"
1920
#include "llvm/Analysis/TargetFolder.h"
2021
#include "llvm/Analysis/TargetLibraryInfo.h"
2122
#include "llvm/Analysis/Utils/Local.h"
@@ -25,6 +26,7 @@
2526
#include "llvm/IR/Constants.h"
2627
#include "llvm/IR/DataLayout.h"
2728
#include "llvm/IR/DerivedTypes.h"
29+
#include "llvm/IR/Dominators.h"
2830
#include "llvm/IR/Function.h"
2931
#include "llvm/IR/GlobalAlias.h"
3032
#include "llvm/IR/GlobalVariable.h"
@@ -590,19 +592,31 @@ Value *llvm::lowerObjectSizeCall(IntrinsicInst *ObjectSize,
590592
const TargetLibraryInfo *TLI,
591593
bool MustSucceed) {
592594
return lowerObjectSizeCall(ObjectSize, DL, TLI, /*AAResults=*/nullptr,
593-
MustSucceed);
595+
/*DT=*/nullptr,
596+
/*AC=*/nullptr, MustSucceed);
594597
}
595598

596599
Value *llvm::lowerObjectSizeCall(
597600
IntrinsicInst *ObjectSize, const DataLayout &DL,
598601
const TargetLibraryInfo *TLI, AAResults *AA, bool MustSucceed,
599602
SmallVectorImpl<Instruction *> *InsertedInstructions) {
603+
return lowerObjectSizeCall(ObjectSize, DL, TLI, AA, /*DT=*/nullptr,
604+
/*AC=*/nullptr, MustSucceed, InsertedInstructions);
605+
}
606+
607+
Value *llvm::lowerObjectSizeCall(
608+
IntrinsicInst *ObjectSize, const DataLayout &DL,
609+
const TargetLibraryInfo *TLI, AAResults *AA, DominatorTree *DT,
610+
AssumptionCache *AC, bool MustSucceed,
611+
SmallVectorImpl<Instruction *> *InsertedInstructions) {
600612
assert(ObjectSize->getIntrinsicID() == Intrinsic::objectsize &&
601613
"ObjectSize must be a call to llvm.objectsize!");
602614

603615
bool MaxVal = cast<ConstantInt>(ObjectSize->getArgOperand(1))->isZero();
604616
ObjectSizeOpts EvalOptions;
605617
EvalOptions.AA = AA;
618+
EvalOptions.DT = DT;
619+
EvalOptions.AC = AC;
606620

607621
// Unless we have to fold this to something, try to be as accurate as
608622
// possible.
@@ -716,7 +730,6 @@ OffsetSpan ObjectSizeOffsetVisitor::computeImpl(Value *V) {
716730
// value that is passed to computeImpl.
717731
IntTyBits = DL.getIndexTypeSizeInBits(V->getType());
718732
Zero = APInt::getZero(IntTyBits);
719-
720733
OffsetSpan ORT = computeValue(V);
721734

722735
bool IndexTypeSizeChanged = InitialIntTyBits != IntTyBits;
@@ -794,6 +807,26 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
794807
Size = Size.umul_ov(NumElems, Overflow);
795808
return Overflow ? ObjectSizeOffsetVisitor::unknown()
796809
: OffsetSpan(Zero, align(Size, I.getAlign()));
810+
} else {
811+
ConstantRange CR = computeConstantRange(ArraySize, /*ForSigned*/ false,
812+
/*UseInstrInfo*/ true,
813+
/*AssumptionCache=*/Options.AC,
814+
/*CtxtI=*/&I, /*DT=*/Options.DT);
815+
if (CR.isFullSet())
816+
return ObjectSizeOffsetVisitor::unknown();
817+
APInt Bound;
818+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
819+
Bound = CR.getUnsignedMax();
820+
// Upper bound actually unknown.
821+
if (Bound.isMaxValue())
822+
return ObjectSizeOffsetVisitor::unknown();
823+
} else {
824+
Bound = CR.getUnsignedMin();
825+
// Lower bound actually unknown.
826+
if (Bound.isMinValue())
827+
return ObjectSizeOffsetVisitor::unknown();
828+
}
829+
return OffsetSpan(Zero, align(Bound, I.getAlign()));
797830
}
798831
return ObjectSizeOffsetVisitor::unknown();
799832
}
@@ -811,7 +844,31 @@ OffsetSpan ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
811844
}
812845

813846
OffsetSpan ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
814-
if (std::optional<APInt> Size = getAllocSize(&CB, TLI))
847+
if (std::optional<APInt> Size =
848+
getAllocSize(&CB, TLI, [&CB, this](const Value *V) -> const Value * {
849+
if (!V->getType()->isIntegerTy())
850+
return V;
851+
ConstantRange CR = computeConstantRange(
852+
V, /*ForSigned*/ false, /*UseInstrInfo*/ true,
853+
/*AssumptionCache=*/Options.AC,
854+
/*CtxtI=*/&CB, /*DT=*/Options.DT);
855+
if (CR.isFullSet())
856+
return V;
857+
858+
APInt Bound;
859+
if (Options.EvalMode == ObjectSizeOpts::Mode::Max) {
860+
Bound = CR.getUnsignedMax();
861+
// Upper bound actually unknown.
862+
if (Bound.isMaxValue())
863+
return V;
864+
} else {
865+
Bound = CR.getUnsignedMin();
866+
// Lower bound actually unknown.
867+
if (Bound.isMinValue())
868+
return V;
869+
}
870+
return ConstantInt::get(V->getType(), Bound);
871+
}))
815872
return OffsetSpan(Zero, *Size);
816873
return ObjectSizeOffsetVisitor::unknown();
817874
}
@@ -856,6 +913,49 @@ OffsetSpan ObjectSizeOffsetVisitor::visitGlobalVariable(GlobalVariable &GV) {
856913
return OffsetSpan(Zero, align(Size, GV.getAlign()));
857914
}
858915

916+
OffsetSpan ObjectSizeOffsetVisitor::visitGetElementPtr(GetElementPtrInst &GEP) {
917+
OffsetSpan PtrData = computeImpl(GEP.getPointerOperand());
918+
if (!PtrData.bothKnown())
919+
return ObjectSizeOffsetVisitor::unknown();
920+
921+
if (Options.EvalMode == ObjectSizeOpts::Mode::Min ||
922+
Options.EvalMode == ObjectSizeOpts::Mode::Max) {
923+
unsigned BitWidth = PtrData.After.getBitWidth();
924+
APInt ConstantOffset = Zero;
925+
SmallMapVector<Value *, APInt, 4> VariableOffsets;
926+
if (!GEP.collectOffset(DL, BitWidth, VariableOffsets, ConstantOffset))
927+
return ObjectSizeOffsetVisitor::unknown();
928+
929+
ConstantRange AccumulatedRange = ConstantOffset;
930+
for (auto const &VO : VariableOffsets) {
931+
ConstantRange CR = computeConstantRange(
932+
VO.first, /*ForSigned*/ true, /*UseInstrInfo*/ true,
933+
/*AssumptionCache=*/Options.AC,
934+
/*CtxtI=*/&GEP, /*DT=*/Options.DT);
935+
if (CR.isFullSet())
936+
return ObjectSizeOffsetVisitor::unknown();
937+
938+
AccumulatedRange = AccumulatedRange.add(CR.multiply(VO.second));
939+
}
940+
941+
APInt Bound;
942+
if (Options.EvalMode == ObjectSizeOpts::Mode::Min) {
943+
Bound = AccumulatedRange.getSignedMax();
944+
// Upper bound actually unknown.
945+
if (Bound.isMaxSignedValue())
946+
return ObjectSizeOffsetVisitor::unknown();
947+
} else {
948+
Bound = AccumulatedRange.getSignedMin();
949+
// Lower bound actually unknown.
950+
if (Bound.isMinSignedValue())
951+
return ObjectSizeOffsetVisitor::unknown();
952+
}
953+
954+
return {PtrData.Before + Bound, PtrData.After - Bound};
955+
}
956+
return ObjectSizeOffsetVisitor::unknown();
957+
}
958+
859959
OffsetSpan ObjectSizeOffsetVisitor::visitIntToPtrInst(IntToPtrInst &) {
860960
// clueless
861961
return ObjectSizeOffsetVisitor::unknown();

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp

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

llvm/lib/Transforms/Scalar/LowerConstantIntrinsics.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "llvm/ADT/PostOrderIterator.h"
1616
#include "llvm/ADT/SetVector.h"
1717
#include "llvm/ADT/Statistic.h"
18+
#include "llvm/Analysis/AssumptionCache.h"
1819
#include "llvm/Analysis/DomTreeUpdater.h"
1920
#include "llvm/Analysis/GlobalsModRef.h"
2021
#include "llvm/Analysis/InstructionSimplify.h"
@@ -99,7 +100,7 @@ static bool replaceConditionalBranchesOnConstant(Instruction *II,
99100
}
100101

101102
bool llvm::lowerConstantIntrinsics(Function &F, const TargetLibraryInfo &TLI,
102-
DominatorTree *DT) {
103+
DominatorTree *DT, AssumptionCache *AC) {
103104
std::optional<DomTreeUpdater> DTU;
104105
if (DT)
105106
DTU.emplace(DT, DomTreeUpdater::UpdateStrategy::Lazy);
@@ -143,7 +144,8 @@ bool llvm::lowerConstantIntrinsics(Function &F, const TargetLibraryInfo &TLI,
143144
IsConstantIntrinsicsHandled++;
144145
break;
145146
case Intrinsic::objectsize:
146-
NewValue = lowerObjectSizeCall(II, DL, &TLI, true);
147+
NewValue = lowerObjectSizeCall(II, DL, &TLI, /*AA=*/nullptr, DT,
148+
/*AC=*/AC, true);
147149
LLVM_DEBUG(dbgs() << "Folding " << *II << " to " << *NewValue << "\n");
148150
ObjectSizeIntrinsicsHandled++;
149151
break;
@@ -159,9 +161,11 @@ bool llvm::lowerConstantIntrinsics(Function &F, const TargetLibraryInfo &TLI,
159161
PreservedAnalyses
160162
LowerConstantIntrinsicsPass::run(Function &F, FunctionAnalysisManager &AM) {
161163
if (lowerConstantIntrinsics(F, AM.getResult<TargetLibraryAnalysis>(F),
162-
AM.getCachedResult<DominatorTreeAnalysis>(F))) {
164+
AM.getCachedResult<DominatorTreeAnalysis>(F),
165+
AM.getCachedResult<AssumptionAnalysis>(F))) {
163166
PreservedAnalyses PA;
164167
PA.preserve<DominatorTreeAnalysis>();
168+
PA.preserve<AssumptionAnalysis>();
165169
return PA;
166170
}
167171

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
2+
; RUN: opt -passes='require<assumptions>',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+
declare void @llvm.assume(i1 %cond)
10+
11+
define i64 @select_alloc_size(i1 %cond) {
12+
; CHECK-LABEL: @select_alloc_size(
13+
; CHECK-NEXT: [[SIZE:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
14+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 [[SIZE]], align 1
15+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 4, i64 3
16+
; CHECK-NEXT: ret i64 [[RES]]
17+
;
18+
%size = select i1 %cond, i64 3, i64 4
19+
%ptr = alloca i8, i64 %size
20+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 false, i1 true, i1 false)
21+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 true, i1 true, i1 false)
22+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
23+
ret i64 %res
24+
}
25+
26+
define i64 @select_malloc_size(i1 %cond) {
27+
; CHECK-LABEL: @select_malloc_size(
28+
; CHECK-NEXT: [[SIZE:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
29+
; CHECK-NEXT: [[PTR:%.*]] = call noalias ptr @malloc(i64 noundef [[SIZE]])
30+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 4, i64 3
31+
; CHECK-NEXT: ret i64 [[RES]]
32+
;
33+
%size = select i1 %cond, i64 3, i64 4
34+
%ptr = call noalias ptr @malloc(i64 noundef %size)
35+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 false, i1 true, i1 false)
36+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 true, i1 true, i1 false)
37+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
38+
ret i64 %res
39+
}
40+
41+
define i64 @assume_malloc_size(i1 %cond, i64 %size) {
42+
; CHECK-LABEL: @assume_malloc_size(
43+
; CHECK-NEXT: entry:
44+
; CHECK-NEXT: [[SHIFTED:%.*]] = add i64 [[SIZE:%.*]], -11
45+
; CHECK-NEXT: [[VALID:%.*]] = icmp ult i64 [[SHIFTED]], -8
46+
; CHECK-NEXT: br i1 [[VALID]], label [[RETURN:%.*]], label [[IF_END:%.*]]
47+
; CHECK: if.end:
48+
; CHECK-NEXT: [[GT:%.*]] = icmp uge i64 [[SIZE]], 9
49+
; CHECK-NEXT: call void @llvm.assume(i1 [[GT]])
50+
; CHECK-NEXT: [[PTR:%.*]] = tail call noalias ptr @malloc(i64 noundef [[SIZE]])
51+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND:%.*]], i64 -1, i64 9
52+
; CHECK-NEXT: br label [[RETURN]]
53+
; CHECK: return:
54+
; CHECK-NEXT: [[RES_PHI:%.*]] = phi i64 [ [[RES]], [[IF_END]] ], [ -1, [[ENTRY:%.*]] ]
55+
; CHECK-NEXT: ret i64 [[RES_PHI]]
56+
;
57+
entry:
58+
%shifted = add i64 %size, -11
59+
%valid = icmp ult i64 %shifted, -8
60+
br i1 %valid, label %return, label %if.end
61+
62+
if.end:
63+
%gt = icmp uge i64 %size, 9
64+
call void @llvm.assume(i1 %gt)
65+
%ptr = tail call noalias ptr @malloc(i64 noundef %size)
66+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 false, i1 false, i1 false)
67+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr, i1 true, i1 false, i1 false)
68+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
69+
br label %return
70+
71+
return:
72+
%res.phi = phi i64 [%res, %if.end], [-1, %entry]
73+
ret i64 %res.phi
74+
}
75+
76+
define i64 @select_gep_offset(i1 %cond) {
77+
; CHECK-LABEL: @select_gep_offset(
78+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 10, align 1
79+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i64 3, i64 4
80+
; CHECK-NEXT: [[PTR_SLIDE:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 [[OFFSET]]
81+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 7, i64 6
82+
; CHECK-NEXT: ret i64 [[RES]]
83+
;
84+
%ptr = alloca i8, i64 10
85+
%offset = select i1 %cond, i64 3, i64 4
86+
%ptr.slide = getelementptr inbounds i8, ptr %ptr, i64 %offset
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+
define i64 @select_gep_neg_offset(i1 %cond) {
94+
; CHECK-LABEL: @select_gep_neg_offset(
95+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 10, align 1
96+
; CHECK-NEXT: [[PTR_SLIDE_1:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 5
97+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i64 -3, i64 -4
98+
; CHECK-NEXT: [[PTR_SLIDE_2:%.*]] = getelementptr inbounds i8, ptr [[PTR_SLIDE_1]], i64 [[OFFSET]]
99+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 9, i64 8
100+
; CHECK-NEXT: ret i64 [[RES]]
101+
;
102+
%ptr = alloca i8, i64 10
103+
%ptr.slide.1 = getelementptr inbounds i8, ptr %ptr, i64 5
104+
%offset = select i1 %cond, i64 -3, i64 -4
105+
%ptr.slide.2 = getelementptr inbounds i8, ptr %ptr.slide.1, i64 %offset
106+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide.2, i1 false, i1 true, i1 false)
107+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide.2, i1 true, i1 true, i1 false)
108+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
109+
ret i64 %res
110+
}
111+
112+
attributes #0 = { nounwind allocsize(0) }

0 commit comments

Comments
 (0)