LibJS: Fix pointer authentication failure in TypedArray

`TypedArray` objects need to know their own constructor objects to allow
copying. This was implemented by storing a function pointer to the
`Intrinsic` object's method which returns the constructor object.

The problem is that function pointers aren't polymorphic, we can't
legally just cast e.g. a `Derived* (*ptr)(void)` to `Base*
(*ptr)(void)` (this is why the code needed a `bit_cast` to even
compile). But this wasn't actually a problem in practice because their
ABIs were the same. But with pointer authentication (Apple's `arm64e`
ABI) this signature mismatch becomes a hard failure and crashes the
process.

Fix this by adding a virtual function that returns the intrinsic
constructor (actually, a `NativeFunction`, as typed arrays constructors
don't inherit from the base `TypedArray` constructor) instead of the
function pointer.

With this, test-js passes and Ladybird launches correctly when built
(with a lot of vcpkg hacks) for arm64e.
This commit is contained in:
Daniel Bertalan 2025-06-17 12:04:16 +02:00 committed by Andrew Kaster
commit edeef940b6
Notes: github-actions[bot] 2025-06-17 21:46:01 +00:00
3 changed files with 14 additions and 13 deletions

View file

@ -351,7 +351,7 @@ ThrowCompletionOr<TypedArrayBase*> typed_array_create_same_type(VM& vm, TypedArr
auto& realm = *vm.current_realm();
// 1. Let constructor be the intrinsic object associated with the constructor name exemplar.[[TypedArrayName]] in Table 68.
auto constructor = (realm.intrinsics().*exemplar.intrinsic_constructor())();
auto constructor = exemplar.intrinsic_constructor(realm);
// 2. Let result be ? TypedArrayCreate(constructor, argumentList).
auto* result = TRY(typed_array_create(vm, constructor, move(arguments)));
@ -468,9 +468,7 @@ void TypedArrayBase::visit_edges(Visitor& visitor)
} \
\
ClassName::ClassName(Object& prototype, u32 length, ArrayBuffer& array_buffer) \
: TypedArray(prototype, \
bit_cast<TypedArrayBase::IntrinsicConstructor>(&Intrinsics::snake_name##_constructor), \
length, array_buffer, Kind::ClassName) \
: TypedArray(prototype, length, array_buffer, Kind::ClassName) \
{ \
if constexpr (#ClassName##sv.is_one_of("BigInt64Array", "BigUint64Array")) \
m_content_type = ContentType::BigInt; \
@ -487,6 +485,11 @@ void TypedArrayBase::visit_edges(Visitor& visitor)
return vm().names.ClassName.as_string(); \
} \
\
GC::Ref<NativeFunction> ClassName::intrinsic_constructor(Realm& realm) const \
{ \
return realm.intrinsics().snake_name##_constructor(); \
} \
\
PrototypeName::PrototypeName(Object& prototype) \
: Object(ConstructWithPrototypeTag::Tag, prototype, MayInterfereWithIndexedPropertyAccess::Yes) \
{ \

View file

@ -35,14 +35,11 @@ public:
#undef __JS_ENUMERATE
};
using IntrinsicConstructor = GC::Ref<TypedArrayConstructor> (Intrinsics::*)();
ByteLength const& array_length() const { return m_array_length; }
ByteLength const& byte_length() const { return m_byte_length; }
u32 byte_offset() const { return m_byte_offset; }
ContentType content_type() const { return m_content_type; }
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
IntrinsicConstructor intrinsic_constructor() const { return m_intrinsic_constructor; }
void set_array_length(ByteLength length) { m_array_length = move(length); }
void set_byte_length(ByteLength length) { m_byte_length = move(length); }
@ -65,12 +62,13 @@ public:
// 25.1.3.19 GetModifySetValueInBuffer ( arrayBuffer, byteIndex, type, value, op ), https://tc39.es/ecma262/#sec-getmodifysetvalueinbuffer
virtual Value get_modify_set_value_in_buffer(size_t byte_index, Value value, ReadWriteModifyFunction operation, bool is_little_endian = true) = 0;
virtual GC::Ref<NativeFunction> intrinsic_constructor(Realm&) const = 0;
protected:
TypedArrayBase(Object& prototype, IntrinsicConstructor intrinsic_constructor, Kind kind, u32 element_size)
TypedArrayBase(Object& prototype, Kind kind, u32 element_size)
: Object(ConstructWithPrototypeTag::Tag, prototype, MayInterfereWithIndexedPropertyAccess::Yes)
, m_element_size(element_size)
, m_kind(kind)
, m_intrinsic_constructor(intrinsic_constructor)
{
set_is_typed_array();
}
@ -82,7 +80,6 @@ protected:
ContentType m_content_type { ContentType::Number };
Kind m_kind {};
GC::Ptr<ArrayBuffer> m_viewed_array_buffer;
IntrinsicConstructor m_intrinsic_constructor { nullptr };
private:
virtual void visit_edges(Visitor&) override;
@ -498,8 +495,8 @@ public:
Value get_modify_set_value_in_buffer(size_t byte_index, Value value, ReadWriteModifyFunction operation, bool is_little_endian = true) override { return viewed_array_buffer()->template get_modify_set_value<T>(byte_index, value, move(operation), is_little_endian); }
protected:
TypedArray(Object& prototype, IntrinsicConstructor intrinsic_constructor, u32 array_length, ArrayBuffer& array_buffer, Kind kind)
: TypedArrayBase(prototype, intrinsic_constructor, kind, sizeof(UnderlyingBufferDataType))
TypedArray(Object& prototype, u32 array_length, ArrayBuffer& array_buffer, Kind kind)
: TypedArrayBase(prototype, kind, sizeof(UnderlyingBufferDataType))
{
VERIFY(!Checked<u32>::multiplication_would_overflow(array_length, sizeof(UnderlyingBufferDataType)));
m_viewed_array_buffer = &array_buffer;
@ -527,6 +524,7 @@ ThrowCompletionOr<double> compare_typed_array_elements(VM&, Value x, Value y, Fu
static ThrowCompletionOr<GC::Ref<ClassName>> create(Realm&, u32 length); \
static GC::Ref<ClassName> create(Realm&, u32 length, ArrayBuffer& buffer); \
virtual FlyString const& element_name() const override; \
virtual GC::Ref<NativeFunction> intrinsic_constructor(Realm&) const override; \
\
protected: \
ClassName(Object& prototype, u32 length, ArrayBuffer& array_buffer); \

View file

@ -97,7 +97,7 @@ static ThrowCompletionOr<TypedArrayBase*> typed_array_species_create(VM& vm, Typ
auto& realm = *vm.current_realm();
// 1. Let defaultConstructor be the intrinsic object listed in column one of Table 72 for exemplar.[[TypedArrayName]].
auto default_constructor = (realm.intrinsics().*exemplar.intrinsic_constructor())();
auto default_constructor = exemplar.intrinsic_constructor(realm);
// 2. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor).
auto* constructor = TRY(species_constructor(vm, exemplar, *default_constructor));