mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 09:07:41 +00:00
AK+LibJS: Handle NaN-boxing pointers on AArch64
JS::Value stores 48 bit pointers to separately allocated objects in its payload. On x86-64, canonical addresses have their top 16 bits set to the same value as bit 47, effectively meaning that the value has to be sign-extended to get the pointer. AArch64, however, expects the topmost bits to be all zeros. This commit gates sign extension behind `#if ARCH(X86_64)`, and adds an `#error` for unsupported architectures, so that we do not forget to think about pointer handling when porting to a new architecture. Fixes #15290 Fixes SerenityOS/ladybird#56
This commit is contained in:
parent
62fed2a31d
commit
2b69af2dfe
4 changed files with 42 additions and 24 deletions
|
@ -18,6 +18,12 @@
|
||||||
# define AK_ARCH_AARCH64 1
|
# define AK_ARCH_AARCH64 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8) || defined(_WIN64)
|
||||||
|
# define AK_ARCH_64_BIT
|
||||||
|
#else
|
||||||
|
# define AK_ARCH_32_BIT
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
# define AK_OS_MACOS
|
# define AK_OS_MACOS
|
||||||
# define AK_OS_BSD_GENERIC
|
# define AK_OS_BSD_GENERIC
|
||||||
|
|
|
@ -39,25 +39,18 @@ TEST_NULLPTR_INPUT(Accessor);
|
||||||
|
|
||||||
#undef TEST_NULLPTR_INPUT
|
#undef TEST_NULLPTR_INPUT
|
||||||
|
|
||||||
// Unfortunately we don't have a way to get the pointer without it being dereferenced
|
|
||||||
// so we just use the same logic, this is dangerous if Value is ever changed!
|
|
||||||
static u64 extract_pointer(u64 ptr)
|
|
||||||
{
|
|
||||||
return (u64)(((i64)(ptr << 16)) >> 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(valid_pointer_in_gives_same_pointer_out)
|
TEST_CASE(valid_pointer_in_gives_same_pointer_out)
|
||||||
{
|
{
|
||||||
if (sizeof(void*) < sizeof(double))
|
if (sizeof(void*) < sizeof(double))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#define EXPECT_POINTER_TO_SURVIVE(input) \
|
#define EXPECT_POINTER_TO_SURVIVE(input) \
|
||||||
{ \
|
{ \
|
||||||
JS::Value value(reinterpret_cast<Object*>(static_cast<u64>(input))); \
|
JS::Value value(reinterpret_cast<Object*>(static_cast<u64>(input))); \
|
||||||
EXPECT(value.is_object()); \
|
EXPECT(value.is_object()); \
|
||||||
EXPECT(!value.is_null()); \
|
EXPECT(!value.is_null()); \
|
||||||
auto extracted_pointer = extract_pointer(value.encoded()); \
|
auto extracted_pointer = JS::Value::extract_pointer_bits(value.encoded()); \
|
||||||
EXPECT_EQ(static_cast<u64>(input), extracted_pointer); \
|
EXPECT_EQ(static_cast<u64>(input), extracted_pointer); \
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_POINTER_TO_SURVIVE(0x1);
|
EXPECT_POINTER_TO_SURVIVE(0x1);
|
||||||
|
@ -66,9 +59,18 @@ TEST_CASE(valid_pointer_in_gives_same_pointer_out)
|
||||||
EXPECT_POINTER_TO_SURVIVE(0x00007fffffffffff);
|
EXPECT_POINTER_TO_SURVIVE(0x00007fffffffffff);
|
||||||
EXPECT_POINTER_TO_SURVIVE(0x0000700000000000);
|
EXPECT_POINTER_TO_SURVIVE(0x0000700000000000);
|
||||||
EXPECT_POINTER_TO_SURVIVE(0x0000100000000000);
|
EXPECT_POINTER_TO_SURVIVE(0x0000100000000000);
|
||||||
|
|
||||||
|
#if ARCH(X86_64)
|
||||||
|
// On x86-64, the top 16 bits of pointers are equal to bit 47.
|
||||||
EXPECT_POINTER_TO_SURVIVE(0xffff800000000000);
|
EXPECT_POINTER_TO_SURVIVE(0xffff800000000000);
|
||||||
EXPECT_POINTER_TO_SURVIVE(0xffff800000000001);
|
EXPECT_POINTER_TO_SURVIVE(0xffff800000000001);
|
||||||
EXPECT_POINTER_TO_SURVIVE(0xffff800000000010);
|
EXPECT_POINTER_TO_SURVIVE(0xffff800000000010);
|
||||||
|
#elif ARCH(AARCH64)
|
||||||
|
// ... but they should contain zeroes on AArch64.
|
||||||
|
EXPECT_POINTER_TO_SURVIVE(0x0000800000000000);
|
||||||
|
EXPECT_POINTER_TO_SURVIVE(0x0000800000000001);
|
||||||
|
EXPECT_POINTER_TO_SURVIVE(0x0000800000000010);
|
||||||
|
#endif
|
||||||
|
|
||||||
#undef EXPECT_POINTER_TO_SURVIVE
|
#undef EXPECT_POINTER_TO_SURVIVE
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ __attribute__((no_sanitize("address"))) void Heap::gather_conservative_roots(Has
|
||||||
// match any pointer-backed tag, in that case we have to extract the pointer to its
|
// match any pointer-backed tag, in that case we have to extract the pointer to its
|
||||||
// canonical form and add that as a possible pointer.
|
// canonical form and add that as a possible pointer.
|
||||||
if ((data & SHIFTED_IS_CELL_PATTERN) == SHIFTED_IS_CELL_PATTERN)
|
if ((data & SHIFTED_IS_CELL_PATTERN) == SHIFTED_IS_CELL_PATTERN)
|
||||||
possible_pointers.set((u64)(((i64)data << 16) >> 16));
|
possible_pointers.set(Value::extract_pointer_bits(data));
|
||||||
else
|
else
|
||||||
possible_pointers.set(data);
|
possible_pointers.set(data);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -398,6 +398,24 @@ public:
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
[[nodiscard]] ALWAYS_INLINE ThrowCompletionOr<Value> invoke(VM&, PropertyKey const& property_key, Args... args);
|
[[nodiscard]] ALWAYS_INLINE ThrowCompletionOr<Value> invoke(VM&, PropertyKey const& property_key, Args... args);
|
||||||
|
|
||||||
|
static constexpr FlatPtr extract_pointer_bits(u64 encoded)
|
||||||
|
{
|
||||||
|
#ifdef AK_ARCH_32_BIT
|
||||||
|
// For 32-bit system the pointer fully fits so we can just return it directly.
|
||||||
|
static_assert(sizeof(void*) == sizeof(u32));
|
||||||
|
return static_cast<FlatPtr>(encoded & 0xffff'ffff);
|
||||||
|
#elif ARCH(X86_64)
|
||||||
|
// For x86_64 the top 16 bits should be sign extending the "real" top bit (47th).
|
||||||
|
// So first shift the top 16 bits away then using the right shift it sign extends the top 16 bits.
|
||||||
|
return static_cast<FlatPtr>((static_cast<i64>(encoded << 16)) >> 16);
|
||||||
|
#elif ARCH(AARCH64)
|
||||||
|
// For AArch64 the top 16 bits of the pointer should be zero.
|
||||||
|
return static_cast<FlatPtr>(encoded & 0xffff'ffff'ffffULL);
|
||||||
|
#else
|
||||||
|
# error "Unknown architecture. Don't know whether pointers need to be sign-extended."
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Value(u64 tag, u64 val)
|
Value(u64 tag, u64 val)
|
||||||
{
|
{
|
||||||
|
@ -444,15 +462,7 @@ private:
|
||||||
PointerType* extract_pointer() const
|
PointerType* extract_pointer() const
|
||||||
{
|
{
|
||||||
VERIFY(is_cell());
|
VERIFY(is_cell());
|
||||||
|
return reinterpret_cast<PointerType*>(extract_pointer_bits(m_value.encoded));
|
||||||
// For 32-bit system the pointer fully fits so we can just return it directly.
|
|
||||||
if constexpr (sizeof(PointerType*) < sizeof(u64))
|
|
||||||
return reinterpret_cast<PointerType*>(static_cast<u32>(m_value.encoded & 0xffffffff));
|
|
||||||
|
|
||||||
// For x86_64 the top 16 bits should be sign extending the "real" top bit (47th).
|
|
||||||
// So first shift the top 16 bits away then using the right shift it sign extends the top 16 bits.
|
|
||||||
u64 ptr_val = (u64)(((i64)(m_value.encoded << 16)) >> 16);
|
|
||||||
return reinterpret_cast<PointerType*>(ptr_val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ThrowCompletionOr<Value> invoke_internal(VM&, PropertyKey const&, Optional<MarkedVector<Value>> arguments);
|
[[nodiscard]] ThrowCompletionOr<Value> invoke_internal(VM&, PropertyKey const&, Optional<MarkedVector<Value>> arguments);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue