diff --git a/API/hermes/TracingRuntime.cpp b/API/hermes/TracingRuntime.cpp index 36dc1cc0c41..986a9698bf8 100644 --- a/API/hermes/TracingRuntime.cpp +++ b/API/hermes/TracingRuntime.cpp @@ -879,6 +879,11 @@ jsi::ArrayBuffer TracingRuntime::createArrayBuffer( throw std::logic_error("Cannot create external ArrayBuffers in trace mode."); } +std::shared_ptr TracingRuntime::getMutableBuffer( + const jsi::ArrayBuffer &buffer) { + throw std::logic_error("Cannot get external ArrayBuffers in trace mode."); +} + size_t TracingRuntime::size(const jsi::Array &arr) { // Array size inquiries read from the length property, which is // non-configurable and thus cannot have side effects. diff --git a/API/hermes/TracingRuntime.h b/API/hermes/TracingRuntime.h index dc26fcb0c4e..8936c1b25c2 100644 --- a/API/hermes/TracingRuntime.h +++ b/API/hermes/TracingRuntime.h @@ -121,6 +121,8 @@ class TracingRuntime : public jsi::RuntimeDecorator { jsi::Array createArray(size_t length) override; jsi::ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override; + std::shared_ptr getMutableBuffer( + const jsi::ArrayBuffer &buffer) override; size_t size(const jsi::Array &arr) override; size_t size(const jsi::ArrayBuffer &buf) override; diff --git a/API/hermes/hermes.cpp b/API/hermes/hermes.cpp index f74e2340f4f..498ebbf9dc9 100644 --- a/API/hermes/hermes.cpp +++ b/API/hermes/hermes.cpp @@ -725,6 +725,8 @@ class HermesRuntimeImpl final : public HermesRuntime, jsi::Array createArray(size_t length) override; jsi::ArrayBuffer createArrayBuffer( std::shared_ptr buffer) override; + std::shared_ptr getMutableBuffer( + const jsi::ArrayBuffer &buffer) override; size_t size(const jsi::Array &) override; size_t size(const jsi::ArrayBuffer &) override; uint8_t *data(const jsi::ArrayBuffer &) override; @@ -2410,6 +2412,25 @@ uint8_t *HermesRuntimeImpl::data(const jsi::ArrayBuffer &arr) { return ab->getDataBlock(runtime_); } +std::shared_ptr HermesRuntimeImpl::getMutableBuffer( + const jsi::ArrayBuffer &arr) { + auto buf = arrayBufferHandle(arr); + if (LLVM_UNLIKELY(!buf->attached())) + throw jsi::JSINativeException("ArrayBuffer is detached."); + + void *context = nullptr; + auto res = vm::JSArrayBuffer::getExternalDataBlock(runtime_, buf, &context); + if (context == nullptr) { + // ArrayBuffer does not hold a MutableBuffer. + return nullptr; + } + checkStatus(res); + + auto mutableBuffer = + reinterpret_cast *>(context); + return *mutableBuffer; +} + jsi::Value HermesRuntimeImpl::getValueAtIndex(const jsi::Array &arr, size_t i) { vm::GCScope gcScope(runtime_); if (LLVM_UNLIKELY(i >= size(arr))) { diff --git a/API/hermes_abi/HermesABIRuntimeWrapper.cpp b/API/hermes_abi/HermesABIRuntimeWrapper.cpp index 405d1af5672..86cbfbc3ba8 100644 --- a/API/hermes_abi/HermesABIRuntimeWrapper.cpp +++ b/API/hermes_abi/HermesABIRuntimeWrapper.cpp @@ -944,6 +944,10 @@ class HermesABIRuntimeWrapper : public Runtime { return intoJSIArrayBuffer(vtable_->create_arraybuffer_from_external_data( abiRt_, new MutableBufferWrapper(std::move(buffer)))); } + std::shared_ptr getMutableBuffer( + const ArrayBuffer &buffer) override { + THROW_UNIMPLEMENTED(); + } size_t size(const Array &arr) override { return vtable_->get_array_length(abiRt_, toABIArray(arr)); } diff --git a/API/hermes_sandbox/HermesSandboxRuntime.cpp b/API/hermes_sandbox/HermesSandboxRuntime.cpp index ded57ed162b..ca828152b78 100644 --- a/API/hermes_sandbox/HermesSandboxRuntime.cpp +++ b/API/hermes_sandbox/HermesSandboxRuntime.cpp @@ -2051,6 +2051,10 @@ class HermesSandboxRuntimeImpl : public facebook::hermes::HermesSandboxRuntime, std::shared_ptr buffer) override { THROW_UNIMPLEMENTED(); } + std::shared_ptr getMutableBuffer( + const ArrayBuffer &buffer) override { + THROW_UNIMPLEMENTED(); + } size_t size(const Array &arr) override { return vt_.get_array_length(this, srt_, toSandboxArray(arr).pointer); } diff --git a/API/jsi/jsi/decorator.h b/API/jsi/jsi/decorator.h index 6410257b250..8e9b16eb5fe 100644 --- a/API/jsi/jsi/decorator.h +++ b/API/jsi/jsi/decorator.h @@ -361,6 +361,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { std::shared_ptr buffer) override { return plain_.createArrayBuffer(std::move(buffer)); }; + std::shared_ptr getMutableBuffer( + const ArrayBuffer &buffer) override { + return plain_.getMutableBuffer(buffer); + } size_t size(const Array& a) override { return plain_.size(a); }; @@ -894,6 +898,10 @@ class WithRuntimeDecorator : public RuntimeDecorator { std::shared_ptr buffer) override { return RD::createArrayBuffer(std::move(buffer)); }; + std::shared_ptr getMutableBuffer( + const ArrayBuffer &buffer) override { + return RD::getMutableBuffer(buffer); + } size_t size(const Array& a) override { Around around{with_}; return RD::size(a); diff --git a/API/jsi/jsi/jsi.h b/API/jsi/jsi/jsi.h index ed8d37f265b..14e7e3e4306 100644 --- a/API/jsi/jsi/jsi.h +++ b/API/jsi/jsi/jsi.h @@ -499,6 +499,8 @@ class JSI_EXPORT Runtime : public ICast { virtual Array createArray(size_t length) = 0; virtual ArrayBuffer createArrayBuffer( std::shared_ptr buffer) = 0; + virtual std::shared_ptr getMutableBuffer( + const ArrayBuffer &buffer) = 0; virtual size_t size(const Array&) = 0; virtual size_t size(const ArrayBuffer&) = 0; virtual uint8_t* data(const ArrayBuffer&) = 0; @@ -1216,7 +1218,7 @@ class JSI_EXPORT ArrayBuffer : public Object { ArrayBuffer(ArrayBuffer&&) = default; ArrayBuffer& operator=(ArrayBuffer&&) = default; - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + ArrayBuffer(Runtime &runtime, std::shared_ptr buffer) : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} /// \return the size of the ArrayBuffer storage. This is not affected by @@ -1234,6 +1236,13 @@ class JSI_EXPORT ArrayBuffer : public Object { return runtime.data(*this); } + /// \return the underlying MutableBuffer if this ArrayBuffer + /// was created with one. + /// This returns nullptr if it does not carry a MutableBuffer. + std::shared_ptr getMutableBuffer(Runtime& runtime) const { + return runtime.getMutableBuffer(*this); + } + private: friend class Object; friend class Value; diff --git a/include/hermes/VM/JSArrayBuffer.h b/include/hermes/VM/JSArrayBuffer.h index 566d939d650..5bbe9e8b397 100644 --- a/include/hermes/VM/JSArrayBuffer.h +++ b/include/hermes/VM/JSArrayBuffer.h @@ -79,6 +79,14 @@ class JSArrayBuffer final : public JSObject { void *context, FinalizeNativeStatePtr finalizePtr); + /// Gets the external data block that this JSArrayBuffer holds if there is + /// any. If this JSArrayBuffer does not hold external data, this returns an + /// error. + static ExecutionStatus getExternalDataBlock( + Runtime &runtime, + Handle self, + void **context); + /// Retrieves a pointer to the held buffer. /// \return A pointer to the buffer owned by this object. This can be null /// if the ArrayBuffer is empty. diff --git a/lib/VM/JSArrayBuffer.cpp b/lib/VM/JSArrayBuffer.cpp index 043b902cf13..ff31e0bd938 100644 --- a/lib/VM/JSArrayBuffer.cpp +++ b/lib/VM/JSArrayBuffer.cpp @@ -274,5 +274,31 @@ ExecutionStatus JSArrayBuffer::setExternalDataBlock( return ExecutionStatus::RETURNED; } +ExecutionStatus JSArrayBuffer::getExternalDataBlock( + Runtime &runtime, + Handle self, + void **context) { + assert(self->attached() && "Buffer must be attached"); + + NamedPropertyDescriptor desc; + bool exists = JSObject::getOwnNamedDescriptor( + self, + runtime, + Predefined::getSymbolID( + Predefined::InternalPropertyArrayBufferExternalFinalizer), + desc); + if (!exists) { + // JSArrayBuffer does not hold an external data block + return ExecutionStatus::EXCEPTION; + } + // Raw pointers below. + NoAllocScope scope(runtime); + NativeState *ns = vmcast( + JSObject::getNamedSlotValueUnsafe(*self, runtime, desc) + .getObject(runtime)); + *context = ns->context(); + return ExecutionStatus::RETURNED; +} + } // namespace vm } // namespace hermes diff --git a/unittests/API/APITest.cpp b/unittests/API/APITest.cpp index 1f2160a0e62..f6793b3c1bd 100644 --- a/unittests/API/APITest.cpp +++ b/unittests/API/APITest.cpp @@ -199,6 +199,26 @@ TEST_F(HermesRuntimeTestMethodsTest, ExternalArrayBufferTest) { rt->instrumentation().collectGarbage(""); EXPECT_TRUE(weakBuf.expired()); } + + { + auto buf = std::make_shared(); + for (uint32_t i = 0; i < buf->arr.size(); i++) + buf->arr[i] = i; + auto arrayBuffer = ArrayBuffer(*rt, buf); + auto roundtrip = eval( + R"#( +(function (buf) { + return buf; +}) +)#"); + Value result = + roundtrip.asObject(*rt).asFunction(*rt).call(*rt, arrayBuffer); + ArrayBuffer arrayBufferAgain = + result.asObject(*rt).getArrayBuffer(*rt); + std::shared_ptr mutableBuffer = + arrayBufferAgain.getMutableBuffer(*rt); + EXPECT_TRUE(mutableBuffer == buf); + } } TEST_F(HermesRuntimeTestMethodsTest, DetachedArrayBuffer) {