Skip to content

Commit 2f1082b

Browse files
[llvm] Improve llvm.objectsize computation by computing GEP, alloca and malloc parameters bound
Using a naive expression walker, it is possible to compute valuable information for allocation functions, GEP and alloca, even in the presence of some dynamic information. We don't rely on computeConstantRange to avoid taking advantage of undefined behavior, which would be counter-productive wrt. usual llvm.objectsize usage. 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 40d0058 commit 2f1082b

File tree

5 files changed

+222
-9
lines changed

5 files changed

+222
-9
lines changed

flang/include/flang/Runtime/descriptor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class alignas(Descriptor) StaticDescriptor {
467467
RT_OFFLOAD_VAR_GROUP_END
468468

469469
RT_API_ATTRS Descriptor &descriptor() {
470+
470471
return *reinterpret_cast<Descriptor *>(storage_);
471472
}
472473
RT_API_ATTRS const Descriptor &descriptor() const {

flang/runtime/io-api-minimal.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ bool IODEF(OutputLogical)(Cookie cookie, bool truth) {
150150
// Provide own definition for `std::__libcpp_verbose_abort` to avoid dependency
151151
// on the version provided by libc++.
152152

153-
void std::__libcpp_verbose_abort(char const *format, ...) {
153+
void std::__libcpp_verbose_abort(char const *format, ...) noexcept {
154154
va_list list;
155155
va_start(list, format);
156156
std::vfprintf(stderr, format, list);

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: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,68 @@ STATISTIC(ObjectVisitorArgument,
670670
STATISTIC(ObjectVisitorLoad,
671671
"Number of load instructions with unsolved size and offset");
672672

673+
static std::optional<APInt>
674+
combinePossibleConstantValues(std::optional<APInt> LHS,
675+
std::optional<APInt> RHS,
676+
ObjectSizeOpts::Mode EvalMode) {
677+
if (!LHS || !RHS)
678+
return std::nullopt;
679+
if (EvalMode == ObjectSizeOpts::Mode::Max)
680+
return LHS->sge(*RHS) ? *LHS : *RHS;
681+
else
682+
return LHS->sle(*RHS) ? *LHS : *RHS;
683+
}
684+
685+
static std::optional<APInt> aggregatePossibleConstantValuesImpl(
686+
const Value *V, ObjectSizeOpts::Mode EvalMode, unsigned recursionDepth) {
687+
constexpr unsigned maxRecursionDepth = 4;
688+
if (recursionDepth == maxRecursionDepth)
689+
return std::nullopt;
690+
691+
if (const auto *CI = dyn_cast<ConstantInt>(V)) {
692+
return CI->getValue();
693+
}
694+
695+
else if (const auto *SI = dyn_cast<SelectInst>(V)) {
696+
return combinePossibleConstantValues(
697+
aggregatePossibleConstantValuesImpl(SI->getTrueValue(), EvalMode,
698+
recursionDepth + 1),
699+
aggregatePossibleConstantValuesImpl(SI->getFalseValue(), EvalMode,
700+
recursionDepth + 1),
701+
EvalMode);
702+
}
703+
else if (const auto *PN = dyn_cast<PHINode>(V)) {
704+
unsigned Count = PN->getNumIncomingValues();
705+
if (Count == 0)
706+
return std::nullopt;
707+
auto Acc = aggregatePossibleConstantValuesImpl(
708+
PN->getIncomingValue(0), EvalMode, recursionDepth + 1);
709+
for (unsigned I = 1; Acc && I < Count; ++I) {
710+
auto Tmp = aggregatePossibleConstantValuesImpl(
711+
PN->getIncomingValue(I), EvalMode, recursionDepth + 1);
712+
Acc = combinePossibleConstantValues(Acc, Tmp, EvalMode);
713+
}
714+
return Acc;
715+
}
716+
717+
return std::nullopt;
718+
}
719+
720+
static std::optional<APInt>
721+
aggregatePossibleConstantValues(const Value *V, ObjectSizeOpts::Mode EvalMode) {
722+
if (auto *CI = dyn_cast<ConstantInt>(V))
723+
return CI->getValue();
724+
725+
if (EvalMode != ObjectSizeOpts::Mode::Min &&
726+
EvalMode != ObjectSizeOpts::Mode::Max)
727+
return std::nullopt;
728+
729+
// Not using computeConstantRange here because we cannot guarantee it's not
730+
// doing optimization based on UB which we want to avoid when expanding
731+
// __builtin_object_size.
732+
return aggregatePossibleConstantValuesImpl(V, EvalMode, 0u);
733+
}
734+
673735
/// Align \p Size according to \p Alignment. If \p Size is greater than
674736
/// getSignedMaxValue(), set it as unknown as we can only represent signed value
675737
/// in OffsetSpan.
@@ -717,11 +779,36 @@ OffsetSpan ObjectSizeOffsetVisitor::computeImpl(Value *V) {
717779
V = V->stripAndAccumulateConstantOffsets(
718780
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true);
719781

782+
// Give it another try with approximated analysis. We don't start with this
783+
// one because stripAndAccumulateConstantOffsets behaves differently wrt.
784+
// overflows if we provide an external Analysis.
785+
if ((Options.EvalMode == ObjectSizeOpts::Mode::Min ||
786+
Options.EvalMode == ObjectSizeOpts::Mode::Max) &&
787+
isa<GEPOperator>(V)) {
788+
// External Analysis used to compute the Min/Max value of individual Offsets
789+
// within a GEP.
790+
ObjectSizeOpts::Mode EvalMode =
791+
Options.EvalMode == ObjectSizeOpts::Mode::Min
792+
? ObjectSizeOpts::Mode::Max
793+
: ObjectSizeOpts::Mode::Min;
794+
auto OffsetRangeAnalysis = [EvalMode](Value &VOffset, APInt &Offset) {
795+
if (auto PossibleOffset =
796+
aggregatePossibleConstantValues(&VOffset, EvalMode)) {
797+
Offset = *PossibleOffset;
798+
return true;
799+
}
800+
return false;
801+
};
802+
803+
V = V->stripAndAccumulateConstantOffsets(
804+
DL, Offset, /* AllowNonInbounds */ true, /* AllowInvariantGroup */ true,
805+
/*ExternalAnalysis=*/OffsetRangeAnalysis);
806+
}
807+
720808
// Later we use the index type size and zero but it will match the type of the
721809
// value that is passed to computeImpl.
722810
IntTyBits = DL.getIndexTypeSizeInBits(V->getType());
723811
Zero = APInt::getZero(IntTyBits);
724-
725812
OffsetSpan ORT = computeValue(V);
726813

727814
bool IndexTypeSizeChanged = InitialIntTyBits != IntTyBits;
@@ -813,8 +900,9 @@ OffsetSpan ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
813900
return OffsetSpan(Zero, align(Size, I.getAlign()));
814901

815902
Value *ArraySize = I.getArraySize();
816-
if (const ConstantInt *C = dyn_cast<ConstantInt>(ArraySize)) {
817-
APInt NumElems = C->getValue();
903+
if (auto PossibleSize =
904+
aggregatePossibleConstantValues(ArraySize, Options.EvalMode)) {
905+
APInt NumElems = *PossibleSize;
818906
if (!CheckedZextOrTrunc(NumElems))
819907
return ObjectSizeOffsetVisitor::unknown();
820908

@@ -840,7 +928,18 @@ OffsetSpan ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
840928
}
841929

842930
OffsetSpan ObjectSizeOffsetVisitor::visitCallBase(CallBase &CB) {
843-
if (std::optional<APInt> Size = getAllocSize(&CB, TLI)) {
931+
auto Mapper = [this](const Value *V) -> const Value * {
932+
if (!V->getType()->isIntegerTy())
933+
return V;
934+
935+
if (auto PossibleBound =
936+
aggregatePossibleConstantValues(V, Options.EvalMode))
937+
return ConstantInt::get(V->getType(), *PossibleBound);
938+
939+
return V;
940+
};
941+
942+
if (std::optional<APInt> Size = getAllocSize(&CB, TLI, Mapper)) {
844943
// Very large unsigned value cannot be represented as OffsetSpan.
845944
if (Size->isNegative())
846945
return ObjectSizeOffsetVisitor::unknown();
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 %c0, i1 %c1) {
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 [[C1:%.*]], 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 %c0, 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 %c1, i64 %objsize_max, i64 %objsize_min
73+
ret i64 %res
74+
}
75+
76+
define i64 @select_neg_oob_offset(i1 %c0, i1 %c1) {
77+
; CHECK-LABEL: @select_neg_oob_offset(
78+
; CHECK-NEXT: [[PTR:%.*]] = alloca i8, i64 10, align 1
79+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[C0:%.*]], i64 -3, i64 -4
80+
; CHECK-NEXT: [[PTR_SLIDE:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 [[OFFSET]]
81+
; CHECK-NEXT: ret i64 0
82+
;
83+
%ptr = alloca i8, i64 10
84+
%offset = select i1 %c0, i64 -3, i64 -4
85+
%ptr.slide = getelementptr inbounds i8, ptr %ptr, i64 %offset
86+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 false, i1 true, i1 false)
87+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 true, i1 true, i1 false)
88+
%res = select i1 %c1, i64 %objsize_max, i64 %objsize_min
89+
ret i64 %res
90+
}
91+
92+
define i64 @select_gep_offsets(i1 %cond) {
93+
; CHECK-LABEL: @select_gep_offsets(
94+
; CHECK-NEXT: [[PTR:%.*]] = alloca [10 x i8], i64 2, align 1
95+
; CHECK-NEXT: [[OFFSET:%.*]] = select i1 [[COND:%.*]], i32 0, i32 1
96+
; CHECK-NEXT: [[PTR_SLIDE:%.*]] = getelementptr inbounds [10 x i8], ptr [[PTR]], i32 [[OFFSET]], i32 5
97+
; CHECK-NEXT: [[RES:%.*]] = select i1 [[COND]], i64 15, i64 5
98+
; CHECK-NEXT: ret i64 [[RES]]
99+
;
100+
%ptr = alloca [10 x i8], i64 2
101+
%offset = select i1 %cond, i32 0, i32 1
102+
%ptr.slide = getelementptr inbounds [10 x i8], ptr %ptr, i32 %offset, i32 5
103+
%objsize_max = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 false, i1 true, i1 false)
104+
%objsize_min = call i64 @llvm.objectsize.i64.p0(ptr %ptr.slide, i1 true, i1 true, i1 false)
105+
%res = select i1 %cond, i64 %objsize_max, i64 %objsize_min
106+
ret i64 %res
107+
}
108+
109+
attributes #0 = { nounwind allocsize(0) }

0 commit comments

Comments
 (0)