Skip to content

Commit e3f936e

Browse files
authored
Don't rely on undefined behavior to store how a User object's allocation is laid out (#105714)
In `User::operator new` a single allocation is created to store the `User` object itself, "intrusive" operands or a pointer for "hung off" operands, and the descriptor. After allocation, details about the layout (number of operands, how the operands are stored, if there is a descriptor) are stored in the `User` object by settings its fields. The `Value` and `User` constructors are then very careful not to initialize these fields so that the values set during allocation can be subsequently read. However, when the `User` object is returned from `operator new` [its value is technically "indeterminate" and so reading a field without first initializing it is undefined behavior (and will be erroneous in C++26)](https://en.cppreference.com/w/cpp/language/default_initialization#Indeterminate_and_erroneous_values). We discovered this issue when trying to build LLVM using MSVC's [`/sdl` flag](https://learn.microsoft.com/en-us/cpp/build/reference/sdl-enable-additional-security-checks?view=msvc-170) which clears class fields after allocation (the docs say that this feature shouldn't be turned on for custom allocators and should only clear pointers, but that doesn't seem to match the implementation). MSVC's behavior both with and without the `/sdl` flag is standards conforming since a program is supposed to initialize storage before reading from it, thus the compiler implementation changing any values will never be observed in a well-formed program. The standard also provides no provisions for making storage bytes not indeterminate by setting them during allocation or `operator new`. The fix for this is to create a set of types that encode the layout and provide these to both `operator new` and the constructor: * The `AllocMarker` types are used to select which `operator new` to use. * `AllocMarker` can then be implicitly converted to a `AllocInfo` which tells the constructor how the type was laid out.
1 parent be770ed commit e3f936e

21 files changed

+508
-368
lines changed

llvm/include/llvm/Analysis/MemorySSA.h

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ class MemoryAccess
218218
inline unsigned getID() const;
219219

220220
MemoryAccess(LLVMContext &C, unsigned Vty, DeleteValueTy DeleteValue,
221-
BasicBlock *BB, unsigned NumOperands)
222-
: DerivedUser(Type::getVoidTy(C), Vty, nullptr, NumOperands, DeleteValue),
221+
BasicBlock *BB, AllocInfo AllocInfo)
222+
: DerivedUser(Type::getVoidTy(C), Vty, AllocInfo, DeleteValue),
223223
Block(BB) {}
224224

225225
// Use deleteValue() to delete a generic MemoryAccess.
@@ -280,8 +280,8 @@ class MemoryUseOrDef : public MemoryAccess {
280280

281281
MemoryUseOrDef(LLVMContext &C, MemoryAccess *DMA, unsigned Vty,
282282
DeleteValueTy DeleteValue, Instruction *MI, BasicBlock *BB,
283-
unsigned NumOperands)
284-
: MemoryAccess(C, Vty, DeleteValue, BB, NumOperands),
283+
AllocInfo AllocInfo)
284+
: MemoryAccess(C, Vty, DeleteValue, BB, AllocInfo),
285285
MemoryInstruction(MI) {
286286
setDefiningAccess(DMA);
287287
}
@@ -307,15 +307,16 @@ class MemoryUseOrDef : public MemoryAccess {
307307
/// MemoryUse's is exactly the set of Instructions for which
308308
/// AliasAnalysis::getModRefInfo returns "Ref".
309309
class MemoryUse final : public MemoryUseOrDef {
310+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
311+
310312
public:
311313
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(MemoryAccess);
312314

313315
MemoryUse(LLVMContext &C, MemoryAccess *DMA, Instruction *MI, BasicBlock *BB)
314-
: MemoryUseOrDef(C, DMA, MemoryUseVal, deleteMe, MI, BB,
315-
/*NumOperands=*/1) {}
316+
: MemoryUseOrDef(C, DMA, MemoryUseVal, deleteMe, MI, BB, AllocMarker) {}
316317

317318
// allocate space for exactly one operand
318-
void *operator new(size_t S) { return User::operator new(S, 1); }
319+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
319320
void operator delete(void *Ptr) { User::operator delete(Ptr); }
320321

321322
static bool classof(const Value *MA) {
@@ -367,19 +368,20 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(MemoryUse, MemoryAccess)
367368
/// associated with them. This use points to the nearest reaching
368369
/// MemoryDef/MemoryPhi.
369370
class MemoryDef final : public MemoryUseOrDef {
371+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{2};
372+
370373
public:
371374
friend class MemorySSA;
372375

373376
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(MemoryAccess);
374377

375378
MemoryDef(LLVMContext &C, MemoryAccess *DMA, Instruction *MI, BasicBlock *BB,
376379
unsigned Ver)
377-
: MemoryUseOrDef(C, DMA, MemoryDefVal, deleteMe, MI, BB,
378-
/*NumOperands=*/2),
380+
: MemoryUseOrDef(C, DMA, MemoryDefVal, deleteMe, MI, BB, AllocMarker),
379381
ID(Ver) {}
380382

381383
// allocate space for exactly two operands
382-
void *operator new(size_t S) { return User::operator new(S, 2); }
384+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
383385
void operator delete(void *Ptr) { User::operator delete(Ptr); }
384386

385387
static bool classof(const Value *MA) {
@@ -474,8 +476,10 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(MemoryUseOrDef, MemoryAccess)
474476
/// Because MemoryUse's do not generate new definitions, they do not have this
475477
/// issue.
476478
class MemoryPhi final : public MemoryAccess {
479+
constexpr static HungOffOperandsAllocMarker AllocMarker{};
480+
477481
// allocate space for exactly zero operands
478-
void *operator new(size_t S) { return User::operator new(S); }
482+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
479483

480484
public:
481485
void operator delete(void *Ptr) { User::operator delete(Ptr); }
@@ -484,7 +488,7 @@ class MemoryPhi final : public MemoryAccess {
484488
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(MemoryAccess);
485489

486490
MemoryPhi(LLVMContext &C, BasicBlock *BB, unsigned Ver, unsigned NumPreds = 0)
487-
: MemoryAccess(C, MemoryPhiVal, deleteMe, BB, 0), ID(Ver),
491+
: MemoryAccess(C, MemoryPhiVal, deleteMe, BB, AllocMarker), ID(Ver),
488492
ReservedSpace(NumPreds) {
489493
allocHungoffUses(ReservedSpace);
490494
}

llvm/include/llvm/IR/Constant.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ class APInt;
4141
/// LLVM Constant Representation
4242
class Constant : public User {
4343
protected:
44-
Constant(Type *ty, ValueTy vty, Use *Ops, unsigned NumOps)
45-
: User(ty, vty, Ops, NumOps) {}
44+
Constant(Type *ty, ValueTy vty, AllocInfo AllocInfo)
45+
: User(ty, vty, AllocInfo) {}
4646

4747
~Constant() = default;
4848

llvm/include/llvm/IR/Constants.h

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,18 @@ template <class ConstantClass> struct ConstantAggrKeyType;
5151
/// Since they can be in use by unrelated modules (and are never based on
5252
/// GlobalValues), it never makes sense to RAUW them.
5353
class ConstantData : public Constant {
54+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{0};
55+
5456
friend class Constant;
5557

5658
Value *handleOperandChangeImpl(Value *From, Value *To) {
5759
llvm_unreachable("Constant data does not have operands!");
5860
}
5961

6062
protected:
61-
explicit ConstantData(Type *Ty, ValueTy VT) : Constant(Ty, VT, nullptr, 0) {}
63+
explicit ConstantData(Type *Ty, ValueTy VT) : Constant(Ty, VT, AllocMarker) {}
6264

63-
void *operator new(size_t S) { return User::operator new(S, 0); }
65+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
6466

6567
public:
6668
void operator delete(void *Ptr) { User::operator delete(Ptr); }
@@ -399,7 +401,8 @@ class ConstantAggregateZero final : public ConstantData {
399401
/// use operands.
400402
class ConstantAggregate : public Constant {
401403
protected:
402-
ConstantAggregate(Type *T, ValueTy VT, ArrayRef<Constant *> V);
404+
ConstantAggregate(Type *T, ValueTy VT, ArrayRef<Constant *> V,
405+
AllocInfo AllocInfo);
403406

404407
public:
405408
/// Transparently provide more efficient getOperand methods.
@@ -425,7 +428,7 @@ class ConstantArray final : public ConstantAggregate {
425428
friend struct ConstantAggrKeyType<ConstantArray>;
426429
friend class Constant;
427430

428-
ConstantArray(ArrayType *T, ArrayRef<Constant *> Val);
431+
ConstantArray(ArrayType *T, ArrayRef<Constant *> Val, AllocInfo AllocInfo);
429432

430433
void destroyConstantImpl();
431434
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -457,7 +460,7 @@ class ConstantStruct final : public ConstantAggregate {
457460
friend struct ConstantAggrKeyType<ConstantStruct>;
458461
friend class Constant;
459462

460-
ConstantStruct(StructType *T, ArrayRef<Constant *> Val);
463+
ConstantStruct(StructType *T, ArrayRef<Constant *> Val, AllocInfo AllocInfo);
461464

462465
void destroyConstantImpl();
463466
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -509,7 +512,7 @@ class ConstantVector final : public ConstantAggregate {
509512
friend struct ConstantAggrKeyType<ConstantVector>;
510513
friend class Constant;
511514

512-
ConstantVector(VectorType *T, ArrayRef<Constant *> Val);
515+
ConstantVector(VectorType *T, ArrayRef<Constant *> Val, AllocInfo AllocInfo);
513516

514517
void destroyConstantImpl();
515518
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -890,9 +893,11 @@ class ConstantTargetNone final : public ConstantData {
890893
class BlockAddress final : public Constant {
891894
friend class Constant;
892895

896+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{2};
897+
893898
BlockAddress(Function *F, BasicBlock *BB);
894899

895-
void *operator new(size_t S) { return User::operator new(S, 2); }
900+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
896901

897902
void destroyConstantImpl();
898903
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -936,9 +941,11 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(BlockAddress, Value)
936941
class DSOLocalEquivalent final : public Constant {
937942
friend class Constant;
938943

944+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
945+
939946
DSOLocalEquivalent(GlobalValue *GV);
940947

941-
void *operator new(size_t S) { return User::operator new(S, 1); }
948+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
942949

943950
void destroyConstantImpl();
944951
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -973,9 +980,11 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(DSOLocalEquivalent, Value)
973980
class NoCFIValue final : public Constant {
974981
friend class Constant;
975982

983+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
984+
976985
NoCFIValue(GlobalValue *GV);
977986

978-
void *operator new(size_t S) { return User::operator new(S, 1); }
987+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
979988

980989
void destroyConstantImpl();
981990
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -1013,10 +1022,12 @@ class ConstantPtrAuth final : public Constant {
10131022
friend struct ConstantPtrAuthKeyType;
10141023
friend class Constant;
10151024

1025+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{4};
1026+
10161027
ConstantPtrAuth(Constant *Ptr, ConstantInt *Key, ConstantInt *Disc,
10171028
Constant *AddrDisc);
10181029

1019-
void *operator new(size_t s) { return User::operator new(s, 4); }
1030+
void *operator new(size_t s) { return User::operator new(s, AllocMarker); }
10201031

10211032
void destroyConstantImpl();
10221033
Value *handleOperandChangeImpl(Value *From, Value *To);
@@ -1102,8 +1113,8 @@ class ConstantExpr : public Constant {
11021113
Value *handleOperandChangeImpl(Value *From, Value *To);
11031114

11041115
protected:
1105-
ConstantExpr(Type *ty, unsigned Opcode, Use *Ops, unsigned NumOps)
1106-
: Constant(ty, ConstantExprVal, Ops, NumOps) {
1116+
ConstantExpr(Type *ty, unsigned Opcode, AllocInfo AllocInfo)
1117+
: Constant(ty, ConstantExprVal, AllocInfo) {
11071118
// Operation type (an Instruction opcode) is stored as the SubclassData.
11081119
setValueSubclassData(Opcode);
11091120
}

llvm/include/llvm/IR/DerivedUser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ class DerivedUser : public User {
3434
DeleteValueTy DeleteValue;
3535

3636
public:
37-
DerivedUser(Type *Ty, unsigned VK, Use *U, unsigned NumOps,
37+
DerivedUser(Type *Ty, unsigned VK, AllocInfo AllocInfo,
3838
DeleteValueTy DeleteValue)
39-
: User(Ty, VK, U, NumOps), DeleteValue(DeleteValue) {}
39+
: User(Ty, VK, AllocInfo), DeleteValue(DeleteValue) {}
4040
};
4141

4242
} // end namespace llvm

llvm/include/llvm/IR/Function.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
7272
using const_arg_iterator = const Argument *;
7373

7474
private:
75+
constexpr static HungOffOperandsAllocMarker AllocMarker{};
76+
7577
// Important things that make up a function!
7678
BasicBlockListType BasicBlocks; ///< The basic blocks
7779

@@ -171,13 +173,14 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
171173
static Function *Create(FunctionType *Ty, LinkageTypes Linkage,
172174
unsigned AddrSpace, const Twine &N = "",
173175
Module *M = nullptr) {
174-
return new Function(Ty, Linkage, AddrSpace, N, M);
176+
return new (AllocMarker) Function(Ty, Linkage, AddrSpace, N, M);
175177
}
176178

177179
// TODO: remove this once all users have been updated to pass an AddrSpace
178180
static Function *Create(FunctionType *Ty, LinkageTypes Linkage,
179181
const Twine &N = "", Module *M = nullptr) {
180-
return new Function(Ty, Linkage, static_cast<unsigned>(-1), N, M);
182+
return new (AllocMarker)
183+
Function(Ty, Linkage, static_cast<unsigned>(-1), N, M);
181184
}
182185

183186
/// Creates a new function and attaches it to a module.

llvm/include/llvm/IR/GlobalAlias.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ template <typename ValueSubClass, typename... Args> class SymbolTableListTraits;
2828
class GlobalAlias : public GlobalValue, public ilist_node<GlobalAlias> {
2929
friend class SymbolTableListTraits<GlobalAlias>;
3030

31+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
32+
3133
GlobalAlias(Type *Ty, unsigned AddressSpace, LinkageTypes Linkage,
3234
const Twine &Name, Constant *Aliasee, Module *Parent);
3335

@@ -59,7 +61,7 @@ class GlobalAlias : public GlobalValue, public ilist_node<GlobalAlias> {
5961
static GlobalAlias *create(const Twine &Name, GlobalValue *Aliasee);
6062

6163
// allocate space for exactly one operand
62-
void *operator new(size_t S) { return User::operator new(S, 1); }
64+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
6365
void operator delete(void *Ptr) { User::operator delete(Ptr); }
6466

6567
/// Provide fast operand accessors

llvm/include/llvm/IR/GlobalIFunc.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ template <typename ValueSubClass, typename... Args> class SymbolTableListTraits;
3434
class GlobalIFunc final : public GlobalObject, public ilist_node<GlobalIFunc> {
3535
friend class SymbolTableListTraits<GlobalIFunc>;
3636

37+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
38+
3739
GlobalIFunc(Type *Ty, unsigned AddressSpace, LinkageTypes Linkage,
3840
const Twine &Name, Constant *Resolver, Module *Parent);
3941

@@ -48,7 +50,7 @@ class GlobalIFunc final : public GlobalObject, public ilist_node<GlobalIFunc> {
4850
Constant *Resolver, Module *Parent);
4951

5052
// allocate space for exactly one operand
51-
void *operator new(size_t S) { return User::operator new(S, 1); }
53+
void *operator new(size_t S) { return User::operator new(S, AllocMarker); }
5254
void operator delete(void *Ptr) { User::operator delete(Ptr); }
5355

5456
/// Provide fast operand accessors

llvm/include/llvm/IR/GlobalObject.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ class GlobalObject : public GlobalValue {
4040
};
4141

4242
protected:
43-
GlobalObject(Type *Ty, ValueTy VTy, Use *Ops, unsigned NumOps,
44-
LinkageTypes Linkage, const Twine &Name,
45-
unsigned AddressSpace = 0)
46-
: GlobalValue(Ty, VTy, Ops, NumOps, Linkage, Name, AddressSpace) {
43+
GlobalObject(Type *Ty, ValueTy VTy, AllocInfo AllocInfo, LinkageTypes Linkage,
44+
const Twine &Name, unsigned AddressSpace = 0)
45+
: GlobalValue(Ty, VTy, AllocInfo, Linkage, Name, AddressSpace) {
4746
setGlobalValueSubClassData(0);
4847
}
4948
~GlobalObject();

llvm/include/llvm/IR/GlobalValue.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ class GlobalValue : public Constant {
7777
};
7878

7979
protected:
80-
GlobalValue(Type *Ty, ValueTy VTy, Use *Ops, unsigned NumOps,
81-
LinkageTypes Linkage, const Twine &Name, unsigned AddressSpace)
82-
: Constant(PointerType::get(Ty, AddressSpace), VTy, Ops, NumOps),
80+
GlobalValue(Type *Ty, ValueTy VTy, AllocInfo AllocInfo, LinkageTypes Linkage,
81+
const Twine &Name, unsigned AddressSpace)
82+
: Constant(PointerType::get(Ty, AddressSpace), VTy, AllocInfo),
8383
ValueType(Ty), Visibility(DefaultVisibility),
8484
UnnamedAddrVal(unsigned(UnnamedAddr::None)),
8585
DllStorageClass(DefaultStorageClass), ThreadLocal(NotThreadLocal),

llvm/include/llvm/IR/GlobalVariable.h

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class DIGlobalVariableExpression;
3939
class GlobalVariable : public GlobalObject, public ilist_node<GlobalVariable> {
4040
friend class SymbolTableListTraits<GlobalVariable>;
4141

42+
constexpr static IntrusiveOperandsAllocMarker AllocMarker{1};
43+
4244
AttributeSet Attrs;
4345

4446
// Is this a global constant?
@@ -70,24 +72,31 @@ class GlobalVariable : public GlobalObject, public ilist_node<GlobalVariable> {
7072
GlobalVariable(const GlobalVariable &) = delete;
7173
GlobalVariable &operator=(const GlobalVariable &) = delete;
7274

75+
private:
76+
/// Set the number of operands on a GlobalVariable.
77+
///
78+
/// GlobalVariable always allocates space for a single operands, but
79+
/// doesn't always use it.
80+
void setGlobalVariableNumOperands(unsigned NumOps) {
81+
assert(NumOps <= 1 && "GlobalVariable can only have 0 or 1 operands");
82+
NumUserOperands = NumOps;
83+
}
84+
85+
public:
7386
~GlobalVariable() {
7487
dropAllReferences();
88+
89+
// Number of operands can be set to 0 after construction and initialization.
90+
// Make sure that number of operands is reset to 1, as this is needed in
91+
// User::operator delete
92+
setGlobalVariableNumOperands(1);
7593
}
7694

7795
// allocate space for exactly one operand
78-
void *operator new(size_t s) {
79-
return User::operator new(s, 1);
80-
}
96+
void *operator new(size_t s) { return User::operator new(s, AllocMarker); }
8197

8298
// delete space for exactly one operand as created in the corresponding new operator
83-
void operator delete(void *ptr){
84-
assert(ptr != nullptr && "must not be nullptr");
85-
User *Obj = static_cast<User *>(ptr);
86-
// Number of operands can be set to 0 after construction and initialization. Make sure
87-
// that number of operands is reset to 1, as this is needed in User::operator delete
88-
Obj->setGlobalVariableNumOperands(1);
89-
User::operator delete(Obj);
90-
}
99+
void operator delete(void *ptr) { User::operator delete(ptr); }
91100

92101
/// Provide fast operand accessors
93102
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Value);

0 commit comments

Comments
 (0)