Skip to content

Commit 2f9cd43

Browse files
authored
[clang][bytecode] Check primitive bit casts for indeterminate bits (#118954)
Record bits ranges of initialized bits and check them in allInitialized().
1 parent d74214c commit 2f9cd43

File tree

6 files changed

+105
-29
lines changed

6 files changed

+105
-29
lines changed

clang/lib/AST/ByteCode/BitcastBuffer.cpp

+51
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88
#include "BitcastBuffer.h"
9+
#include "llvm/ADT/STLExtras.h"
910

1011
using namespace clang;
1112
using namespace clang::interp;
@@ -60,6 +61,56 @@ BitcastBuffer::copyBits(Bits BitOffset, Bits BitWidth, Bits FullBitWidth,
6061
return Out;
6162
}
6263

64+
bool BitcastBuffer::allInitialized() const {
65+
Bits Sum;
66+
for (BitRange BR : InitializedBits)
67+
Sum += BR.size();
68+
69+
return Sum == FinalBitSize;
70+
}
71+
72+
void BitcastBuffer::markInitialized(Bits Offset, Bits Length) {
73+
if (Length.isZero())
74+
return;
75+
76+
BitRange Element(Offset, Offset + Length - Bits(1));
77+
if (InitializedBits.empty()) {
78+
InitializedBits.push_back(Element);
79+
return;
80+
}
81+
82+
assert(InitializedBits.size() >= 1);
83+
// Common case of just appending.
84+
Bits End = InitializedBits.back().End;
85+
if (End <= Offset) {
86+
// Merge this range with the last one.
87+
// In the best-case scenario, this means we only ever have
88+
// one single bit range covering all bits.
89+
if (End == (Offset - Bits(1))) {
90+
InitializedBits.back().End = Element.End;
91+
return;
92+
}
93+
94+
// Otherwise, we can simply append.
95+
InitializedBits.push_back(Element);
96+
} else {
97+
// Insert sorted.
98+
auto It = std::upper_bound(InitializedBits.begin(), InitializedBits.end(),
99+
Element);
100+
InitializedBits.insert(It, Element);
101+
}
102+
103+
#ifndef NDEBUG
104+
// Ensure ranges are sorted and non-overlapping.
105+
assert(llvm::is_sorted(InitializedBits));
106+
for (unsigned I = 1; I != InitializedBits.size(); ++I) {
107+
[[maybe_unused]] auto Prev = InitializedBits[I - 1];
108+
[[maybe_unused]] auto Cur = InitializedBits[I];
109+
assert(Prev.End.N < Cur.Start.N);
110+
}
111+
#endif
112+
}
113+
63114
#if 0
64115
template<typename T>
65116
static std::string hex(T t) {

clang/lib/AST/ByteCode/BitcastBuffer.h

+24-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifndef LLVM_CLANG_AST_INTERP_BITCAST_BUFFER_H
99
#define LLVM_CLANG_AST_INTERP_BITCAST_BUFFER_H
1010

11+
#include "llvm/ADT/SmallVector.h"
1112
#include <cassert>
1213
#include <cstddef>
1314
#include <memory>
@@ -30,14 +31,20 @@ struct Bits {
3031
bool nonZero() const { return N != 0; }
3132
bool isZero() const { return N == 0; }
3233

33-
Bits operator-(Bits Other) { return Bits(N - Other.N); }
34-
Bits operator+(Bits Other) { return Bits(N + Other.N); }
34+
Bits operator-(Bits Other) const { return Bits(N - Other.N); }
35+
Bits operator+(Bits Other) const { return Bits(N + Other.N); }
3536
Bits operator+=(size_t O) {
3637
N += O;
3738
return *this;
3839
}
40+
Bits operator+=(Bits O) {
41+
N += O.N;
42+
return *this;
43+
}
3944

40-
bool operator>=(Bits Other) { return N >= Other.N; }
45+
bool operator>=(Bits Other) const { return N >= Other.N; }
46+
bool operator<=(Bits Other) const { return N <= Other.N; }
47+
bool operator==(Bits Other) const { return N == Other.N; }
4148
};
4249

4350
/// A quantity in bytes.
@@ -48,11 +55,21 @@ struct Bytes {
4855
Bits toBits() const { return Bits(N * 8); }
4956
};
5057

58+
struct BitRange {
59+
Bits Start;
60+
Bits End;
61+
62+
BitRange(Bits Start, Bits End) : Start(Start), End(End) {}
63+
Bits size() const { return End - Start + Bits(1); }
64+
bool operator<(BitRange Other) const { return Start.N < Other.Start.N; }
65+
};
66+
5167
/// Track what bits have been initialized to known values and which ones
5268
/// have indeterminate value.
5369
struct BitcastBuffer {
5470
Bits FinalBitSize;
5571
std::unique_ptr<std::byte[]> Data;
72+
llvm::SmallVector<BitRange> InitializedBits;
5673

5774
BitcastBuffer(Bits FinalBitSize) : FinalBitSize(FinalBitSize) {
5875
assert(FinalBitSize.isFullByte());
@@ -64,10 +81,10 @@ struct BitcastBuffer {
6481
Bits size() const { return FinalBitSize; }
6582

6683
/// Returns \c true if all bits in the buffer have been initialized.
67-
bool allInitialized() const {
68-
// FIXME: Implement.
69-
return true;
70-
}
84+
bool allInitialized() const;
85+
/// Marks the bits in the given range as initialized.
86+
/// FIXME: Can we do this automatically in pushData()?
87+
void markInitialized(Bits Start, Bits Length);
7188

7289
/// Push \p BitWidth bits at \p BitOffset from \p In into the buffer.
7390
/// \p TargetEndianness is the endianness of the target we're compiling for.

clang/lib/AST/ByteCode/Compiler.cpp

+1-6
Original file line numberDiff line numberDiff line change
@@ -6483,19 +6483,14 @@ bool Compiler<Emitter>::emitBuiltinBitCast(const CastExpr *E) {
64836483
QualType ToType = E->getType();
64846484
std::optional<PrimType> ToT = classify(ToType);
64856485

6486+
// Bitcasting TO nullptr_t is always fine.
64866487
if (ToType->isNullPtrType()) {
64876488
if (!this->discard(SubExpr))
64886489
return false;
64896490

64906491
return this->emitNullPtr(0, nullptr, E);
64916492
}
64926493

6493-
if (FromType->isNullPtrType() && ToT) {
6494-
if (!this->discard(SubExpr))
6495-
return false;
6496-
6497-
return visitZeroInitializer(*ToT, ToType, E);
6498-
}
64996494
assert(!ToType->isReferenceType());
65006495

65016496
// Prepare storage for the result in case we discard.

clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
287287
}
288288

289289
Buffer.pushData(Buff.get(), BitOffset, BitWidth, TargetEndianness);
290+
Buffer.markInitialized(BitOffset, BitWidth);
290291
return true;
291292
});
292293
}

clang/test/AST/ByteCode/builtin-bit-cast-bitfields.cpp

+21-2
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ namespace BitFields {
134134
enum byte : unsigned char {};
135135

136136
constexpr BF bf = {0x3};
137-
/// Requires bitcasts to composite types.
138137
static_assert(bit_cast<bits<2>>(bf).bits == bf.z);
139138
static_assert(bit_cast<unsigned char>(bf));
140139

141-
static_assert(__builtin_bit_cast(byte, bf));
140+
static_assert(__builtin_bit_cast(byte, bf)); // expected-error {{not an integral constant expression}} \
141+
// expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'byte' is invalid}}
142142

143143
struct M {
144144
// ref-note@+1 {{subobject declared here}}
@@ -439,3 +439,22 @@ namespace Enums {
439439
static_assert(
440440
bit_cast<X>((unsigned char)0x40).direction == X::direction::right);
441441
}
442+
443+
namespace IndeterminateBits {
444+
struct S {
445+
unsigned a : 13;
446+
unsigned : 17;
447+
unsigned b : 2;
448+
};
449+
constexpr unsigned A = __builtin_bit_cast(unsigned, S{12, 3}); // expected-error {{must be initialized by a constant expression}} \
450+
// expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'unsigned int' is invalid}}
451+
452+
453+
/// GCC refuses to compile this as soon as we access the indeterminate bits
454+
/// in the static_assert. MSVC accepts it.
455+
struct S2 {
456+
unsigned char a : 2;
457+
};
458+
constexpr unsigned char B = __builtin_bit_cast(unsigned char, S2{3});
459+
static_assert(B == (LITTLE_END ? 3 : 192));
460+
}

clang/test/AST/ByteCode/builtin-bit-cast.cpp

+7-14
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,8 @@ namespace simple {
130130
static_assert(check_round_trip<unsigned>((int)0x0C05FEFE));
131131
static_assert(round_trip<float>((int)0x0C05FEFE));
132132

133-
134-
/// This works in GCC and in the bytecode interpreter, but the current interpreter
135-
/// diagnoses it.
136-
/// FIXME: Should also be rejected in the bytecode interpreter.
137-
static_assert(__builtin_bit_cast(intptr_t, nullptr) == 0); // ref-error {{not an integral constant expression}} \
138-
// ref-note {{indeterminate value can only initialize an object}}
133+
static_assert(__builtin_bit_cast(intptr_t, nullptr) == 0); // both-error {{not an integral constant expression}} \
134+
// both-note {{indeterminate value can only initialize an object}}
139135

140136
constexpr int test_from_nullptr_pass = (__builtin_bit_cast(unsigned char[sizeof(nullptr)], nullptr), 0);
141137
constexpr unsigned char NPData[sizeof(nullptr)] = {1,2,3,4};
@@ -394,7 +390,6 @@ void bad_types() {
394390
};
395391
static_assert(__builtin_bit_cast(int, X{0}) == 0); // both-error {{not an integral constant expression}} \
396392
// both-note {{bit_cast from a union type is not allowed in a constant expression}}
397-
#if 1
398393

399394
struct G {
400395
int g;
@@ -405,19 +400,17 @@ void bad_types() {
405400
// both-error@+2 {{constexpr variable 'x' must be initialized by a constant expression}}
406401
// both-note@+1 {{bit_cast to a union type is not allowed in a constant expression}}
407402
constexpr X x = __builtin_bit_cast(X, G{0});
408-
#endif
403+
409404
struct has_pointer {
410-
int *ptr; // both-note {{invalid type 'int *' is a member of 'has_pointer'}}
405+
int *ptr; // both-note 2{{invalid type 'int *' is a member of 'has_pointer'}}
411406
};
412407

413408
constexpr intptr_t ptr = __builtin_bit_cast(intptr_t, has_pointer{0}); // both-error {{constexpr variable 'ptr' must be initialized by a constant expression}} \
414409
// both-note {{bit_cast from a pointer type is not allowed in a constant expression}}
415410

416-
#if 0
417-
// expected-error@+2 {{constexpr variable 'hptr' must be initialized by a constant expression}}
418-
// expected-note@+1 {{bit_cast to a pointer type is not allowed in a constant expression}}
419-
constexpr has_pointer hptr = __builtin_bit_cast(has_pointer, 0ul);
420-
#endif
411+
// both-error@+2 {{constexpr variable 'hptr' must be initialized by a constant expression}}
412+
// both-note@+1 {{bit_cast to a pointer type is not allowed in a constant expression}}
413+
constexpr has_pointer hptr = __builtin_bit_cast(has_pointer, (intptr_t)0);
421414
}
422415

423416
void test_array_fill() {

0 commit comments

Comments
 (0)