Serenity Operating System
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Daniel Bertalan and committed by
Andreas Kling
2b69af2d 62fed2a3

+42 -24
+6
AK/Platform.h
··· 18 18 # define AK_ARCH_AARCH64 1 19 19 #endif 20 20 21 + #if (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8) || defined(_WIN64) 22 + # define AK_ARCH_64_BIT 23 + #else 24 + # define AK_ARCH_32_BIT 25 + #endif 26 + 21 27 #if defined(__APPLE__) && defined(__MACH__) 22 28 # define AK_OS_MACOS 23 29 # define AK_OS_BSD_GENERIC
+16 -14
Tests/LibJS/test-value-js.cpp
··· 39 39 40 40 #undef TEST_NULLPTR_INPUT 41 41 42 - // Unfortunately we don't have a way to get the pointer without it being dereferenced 43 - // so we just use the same logic, this is dangerous if Value is ever changed! 44 - static u64 extract_pointer(u64 ptr) 45 - { 46 - return (u64)(((i64)(ptr << 16)) >> 16); 47 - } 48 - 49 42 TEST_CASE(valid_pointer_in_gives_same_pointer_out) 50 43 { 51 44 if (sizeof(void*) < sizeof(double)) 52 45 return; 53 46 54 - #define EXPECT_POINTER_TO_SURVIVE(input) \ 55 - { \ 56 - JS::Value value(reinterpret_cast<Object*>(static_cast<u64>(input))); \ 57 - EXPECT(value.is_object()); \ 58 - EXPECT(!value.is_null()); \ 59 - auto extracted_pointer = extract_pointer(value.encoded()); \ 60 - EXPECT_EQ(static_cast<u64>(input), extracted_pointer); \ 47 + #define EXPECT_POINTER_TO_SURVIVE(input) \ 48 + { \ 49 + JS::Value value(reinterpret_cast<Object*>(static_cast<u64>(input))); \ 50 + EXPECT(value.is_object()); \ 51 + EXPECT(!value.is_null()); \ 52 + auto extracted_pointer = JS::Value::extract_pointer_bits(value.encoded()); \ 53 + EXPECT_EQ(static_cast<u64>(input), extracted_pointer); \ 61 54 } 62 55 63 56 EXPECT_POINTER_TO_SURVIVE(0x1); ··· 66 59 EXPECT_POINTER_TO_SURVIVE(0x00007fffffffffff); 67 60 EXPECT_POINTER_TO_SURVIVE(0x0000700000000000); 68 61 EXPECT_POINTER_TO_SURVIVE(0x0000100000000000); 62 + 63 + #if ARCH(X86_64) 64 + // On x86-64, the top 16 bits of pointers are equal to bit 47. 69 65 EXPECT_POINTER_TO_SURVIVE(0xffff800000000000); 70 66 EXPECT_POINTER_TO_SURVIVE(0xffff800000000001); 71 67 EXPECT_POINTER_TO_SURVIVE(0xffff800000000010); 68 + #elif ARCH(AARCH64) 69 + // ... but they should contain zeroes on AArch64. 70 + EXPECT_POINTER_TO_SURVIVE(0x0000800000000000); 71 + EXPECT_POINTER_TO_SURVIVE(0x0000800000000001); 72 + EXPECT_POINTER_TO_SURVIVE(0x0000800000000010); 73 + #endif 72 74 73 75 #undef EXPECT_POINTER_TO_SURVIVE 74 76 }
+1 -1
Userland/Libraries/LibJS/Heap/Heap.cpp
··· 142 142 // match any pointer-backed tag, in that case we have to extract the pointer to its 143 143 // canonical form and add that as a possible pointer. 144 144 if ((data & SHIFTED_IS_CELL_PATTERN) == SHIFTED_IS_CELL_PATTERN) 145 - possible_pointers.set((u64)(((i64)data << 16) >> 16)); 145 + possible_pointers.set(Value::extract_pointer_bits(data)); 146 146 else 147 147 possible_pointers.set(data); 148 148 } else {
+19 -9
Userland/Libraries/LibJS/Runtime/Value.h
··· 398 398 template<typename... Args> 399 399 [[nodiscard]] ALWAYS_INLINE ThrowCompletionOr<Value> invoke(VM&, PropertyKey const& property_key, Args... args); 400 400 401 + static constexpr FlatPtr extract_pointer_bits(u64 encoded) 402 + { 403 + #ifdef AK_ARCH_32_BIT 404 + // For 32-bit system the pointer fully fits so we can just return it directly. 405 + static_assert(sizeof(void*) == sizeof(u32)); 406 + return static_cast<FlatPtr>(encoded & 0xffff'ffff); 407 + #elif ARCH(X86_64) 408 + // For x86_64 the top 16 bits should be sign extending the "real" top bit (47th). 409 + // So first shift the top 16 bits away then using the right shift it sign extends the top 16 bits. 410 + return static_cast<FlatPtr>((static_cast<i64>(encoded << 16)) >> 16); 411 + #elif ARCH(AARCH64) 412 + // For AArch64 the top 16 bits of the pointer should be zero. 413 + return static_cast<FlatPtr>(encoded & 0xffff'ffff'ffffULL); 414 + #else 415 + # error "Unknown architecture. Don't know whether pointers need to be sign-extended." 416 + #endif 417 + } 418 + 401 419 private: 402 420 Value(u64 tag, u64 val) 403 421 { ··· 444 462 PointerType* extract_pointer() const 445 463 { 446 464 VERIFY(is_cell()); 447 - 448 - // For 32-bit system the pointer fully fits so we can just return it directly. 449 - if constexpr (sizeof(PointerType*) < sizeof(u64)) 450 - return reinterpret_cast<PointerType*>(static_cast<u32>(m_value.encoded & 0xffffffff)); 451 - 452 - // For x86_64 the top 16 bits should be sign extending the "real" top bit (47th). 453 - // So first shift the top 16 bits away then using the right shift it sign extends the top 16 bits. 454 - u64 ptr_val = (u64)(((i64)(m_value.encoded << 16)) >> 16); 455 - return reinterpret_cast<PointerType*>(ptr_val); 465 + return reinterpret_cast<PointerType*>(extract_pointer_bits(m_value.encoded)); 456 466 } 457 467 458 468 [[nodiscard]] ThrowCompletionOr<Value> invoke_internal(VM&, PropertyKey const&, Optional<MarkedVector<Value>> arguments);