Rewrite CompactValue to avoid undefined behavior from the use of a union for type-punning #1154

Closed
htpiv wants to merge 4 commits from htpiv/use-bit-cast into main

View File

@@ -9,6 +9,11 @@
#ifdef __cplusplus #ifdef __cplusplus
#ifdef __cpp_lib_bit_cast
#include <bit>
#else
#include <cstring>
#endif
rozele commented 2022-07-25 10:01:53 -07:00 (Migrated from github.com)
Review

Looks like internal builds are failing because std::memcpy requires #include <cstring>

Looks like internal builds are failing because std::memcpy requires `#include <cstring>`
#include "YGValue.h" #include "YGValue.h"
#include "YGMacros.h" #include "YGMacros.h"
#include <cmath> #include <cmath>
@@ -55,30 +60,28 @@ public:
if (value == 0.0f || (value < LOWER_BOUND && value > -LOWER_BOUND)) { if (value == 0.0f || (value < LOWER_BOUND && value > -LOWER_BOUND)) {
constexpr auto zero = constexpr auto zero =
Unit == YGUnitPercent ? ZERO_BITS_PERCENT : ZERO_BITS_POINT; Unit == YGUnitPercent ? ZERO_BITS_PERCENT : ZERO_BITS_POINT;
return {Payload{zero}}; return {zero};
} }
constexpr auto upperBound = constexpr auto upperBound = Unit == YGUnitPercent ? UPPER_BOUND_PERCENT : UPPER_BOUND_POINT;
Unit == YGUnitPercent ? UPPER_BOUND_PERCENT : UPPER_BOUND_POINT;
if (value > upperBound || value < -upperBound) { if (value > upperBound || value < -upperBound) {
value = copysignf(upperBound, value); value = copysignf(upperBound, value);
} }
uint32_t unitBit = Unit == YGUnitPercent ? PERCENT_BIT : 0; uint32_t unitBit = Unit == YGUnitPercent ? PERCENT_BIT : 0;
auto data = Payload{value}; auto data = asU32(value);
data.repr -= BIAS; data -= BIAS;
data.repr |= unitBit; data |= unitBit;
return {data}; return {data};
} }
template <YGUnit Unit> template <YGUnit Unit>
static CompactValue ofMaybe(float value) noexcept { static CompactValue ofMaybe(float value) noexcept {
return std::isnan(value) || std::isinf(value) ? ofUndefined() return std::isnan(value) || std::isinf(value) ? ofUndefined() : of<Unit>(value);
: of<Unit>(value);
} }
static constexpr CompactValue ofZero() noexcept { static constexpr CompactValue ofZero() noexcept {
return CompactValue{Payload{ZERO_BITS_POINT}}; return CompactValue{ZERO_BITS_POINT};
} }
static constexpr CompactValue ofUndefined() noexcept { static constexpr CompactValue ofUndefined() noexcept {
@@ -86,13 +89,12 @@ public:
} }
static constexpr CompactValue ofAuto() noexcept { static constexpr CompactValue ofAuto() noexcept {
return CompactValue{Payload{AUTO_BITS}}; return CompactValue{AUTO_BITS};
} }
constexpr CompactValue() noexcept constexpr CompactValue() noexcept : repr_(0x7FC00000) {}
: payload_(std::numeric_limits<float>::quiet_NaN()) {}
CompactValue(const YGValue& x) noexcept : payload_(uint32_t{0}) { CompactValue(const YGValue &x) noexcept : repr_(uint32_t{0}) {
switch (x.unit) { switch (x.unit) {
case YGUnitUndefined: case YGUnitUndefined:
*this = ofUndefined(); *this = ofUndefined();
@@ -110,7 +112,7 @@ public:
} }
operator YGValue() const noexcept { operator YGValue() const noexcept {
switch (payload_.repr) { switch (repr_) {
case AUTO_BITS: case AUTO_BITS:
return YGValueAuto; return YGValueAuto;
case ZERO_BITS_POINT: case ZERO_BITS_POINT:
@@ -119,34 +121,27 @@ public:
return YGValue{0.0f, YGUnitPercent}; return YGValue{0.0f, YGUnitPercent};
} }
if (std::isnan(payload_.value)) { if (std::isnan(asFloat(repr_))) {
return YGValueUndefined; return YGValueUndefined;
} }
auto data = payload_; auto data = repr_;
data.repr &= ~PERCENT_BIT; data &= ~PERCENT_BIT;
data.repr += BIAS; data += BIAS;
return YGValue{ return YGValue{asFloat(data), repr_ & 0x40000000 ? YGUnitPercent : YGUnitPoint};
data.value, payload_.repr & 0x40000000 ? YGUnitPercent : YGUnitPoint};
} }
bool isUndefined() const noexcept { bool isUndefined() const noexcept {
return ( return (repr_ != AUTO_BITS && repr_ != ZERO_BITS_POINT && repr_ != ZERO_BITS_PERCENT && std::isnan(asFloat(repr_)));
payload_.repr != AUTO_BITS && payload_.repr != ZERO_BITS_POINT &&
payload_.repr != ZERO_BITS_PERCENT && std::isnan(payload_.value));
} }
bool isAuto() const noexcept { return payload_.repr == AUTO_BITS; } bool isAuto() const noexcept {
return repr_ == AUTO_BITS;
}
private: private:
union Payload { uint32_t repr_;
float value;
uint32_t repr;
Payload() = delete;
constexpr Payload(uint32_t r) : repr(r) {}
constexpr Payload(float v) : value(v) {}
};
static constexpr uint32_t BIAS = 0x20000000; static constexpr uint32_t BIAS = 0x20000000;
static constexpr uint32_t PERCENT_BIT = 0x40000000; static constexpr uint32_t PERCENT_BIT = 0x40000000;
@@ -157,13 +152,36 @@ private:
static constexpr uint32_t ZERO_BITS_POINT = 0x7f8f0f0f; static constexpr uint32_t ZERO_BITS_POINT = 0x7f8f0f0f;
static constexpr uint32_t ZERO_BITS_PERCENT = 0x7f80f0f0; static constexpr uint32_t ZERO_BITS_PERCENT = 0x7f80f0f0;
constexpr CompactValue(Payload data) noexcept : payload_(data) {} constexpr CompactValue(uint32_t data) noexcept : repr_(data) {}
Payload payload_; VISIBLE_FOR_TESTING uint32_t repr() {
return repr_;
}
VISIBLE_FOR_TESTING uint32_t repr() { return payload_.repr; } static uint32_t asU32(float f) {
#ifdef __cpp_lib_bit_cast
return std::bit_cast<uint32_t>(f);
#else
uint32_t u;
static_assert(sizeof(u) == sizeof(f), "uint32_t and float must have the same size");
std::memcpy(&u, &f, sizeof(f));
return u;
#endif
}
static float asFloat(uint32_t u) {
#ifdef __cpp_lib_bit_cast
return std::bit_cast<float>(u);
#else
float f;
static_assert(sizeof(f) == sizeof(u), "uint32_t and float must have the same size");
std::memcpy(&f, &u, sizeof(u));
return f;
#endif
}
}; };
template <> template <>
CompactValue CompactValue::of<YGUnitUndefined>(float) noexcept = delete; CompactValue CompactValue::of<YGUnitUndefined>(float) noexcept = delete;
template <> template <>
@@ -174,7 +192,7 @@ template <>
CompactValue CompactValue::ofMaybe<YGUnitAuto>(float) noexcept = delete; CompactValue CompactValue::ofMaybe<YGUnitAuto>(float) noexcept = delete;
constexpr bool operator==(CompactValue a, CompactValue b) noexcept { constexpr bool operator==(CompactValue a, CompactValue b) noexcept {
return a.payload_.repr == b.payload_.repr; return a.repr_ == b.repr_;
} }
constexpr bool operator!=(CompactValue a, CompactValue b) noexcept { constexpr bool operator!=(CompactValue a, CompactValue b) noexcept {