diff --git a/packages/react-native/React/Base/RCTArrayBuffer.h b/packages/react-native/React/Base/RCTArrayBuffer.h index c8d80878819..d60b5cc82f7 100644 --- a/packages/react-native/React/Base/RCTArrayBuffer.h +++ b/packages/react-native/React/Base/RCTArrayBuffer.h @@ -17,7 +17,11 @@ NS_ASSUME_NONNULL_BEGIN * - `YES` — safe to retain and use from any thread (synchronize if aliasing JS * memory). * - `NO` — valid only during the synchronous call on the calling thread; copy with - * `arrayBufferWithCopiedBytes:length:` to keep the bytes. + * `arrayBufferWithCopiedBytes:length:` to keep the bytes. Such a buffer must not be captured in + * a block or handed to a callback or promise resolve block, which deliver after the call + * returns. The TurboModule framework copies rather than aliases whenever the method signature + * exposes a block parameter, and buffers nested inside `NSArray` or `NSDictionary` arguments + * always own their bytes. */ @interface RCTArrayBuffer : NSObject diff --git a/packages/react-native/React/Base/RCTArrayBuffer.mm b/packages/react-native/React/Base/RCTArrayBuffer.mm index c4270b927be..27b4801e223 100644 --- a/packages/react-native/React/Base/RCTArrayBuffer.mm +++ b/packages/react-native/React/Base/RCTArrayBuffer.mm @@ -23,13 +23,14 @@ - (instancetype)initWithCopiedBytes:(const void *_Nullable)bytes length:(NSUInte @end @implementation RCTArrayBuffer { - void *_bytes; - NSUInteger _length; - BOOL _owningBytes; void (^_cleanup)(void); std::vector _copiedBytes; } +@synthesize mutableBytes = _bytes; +@synthesize length = _length; +@synthesize owningBytes = _owningBytes; + #pragma mark - Initializers - (instancetype)initWithBytesNoCopy:(void *)bytes @@ -38,12 +39,17 @@ - (instancetype)initWithBytesNoCopy:(void *)bytes cleanup:(void (^)(void))cleanup { if (bytes == NULL && length != 0) { + // The caller handed ownership of `bytes` to `cleanup`; nothing else would release them. + if (cleanup != nil) { + cleanup(); + } [NSException raise:NSInvalidArgumentException format:@"RCTArrayBuffer: NULL bytes with length %lu", (unsigned long)length]; } - if (self = [super init]) { - _bytes = bytes; + if ((self = [super init]) != nil) { + // `mutableBytes` is documented as NULL exactly when `length` is 0. + _bytes = length == 0 ? NULL : bytes; _length = length; _owningBytes = owningBytes; _cleanup = [cleanup copy]; @@ -63,7 +69,7 @@ - (instancetype)initWithCopiedBytes:(const void *)bytes length:(NSUInteger)lengt } // Moving a vector hands over its heap buffer, so `data()` stays valid in `_copiedBytes`. - if (self = [self initWithBytesNoCopy:copy.data() length:length owningBytes:YES cleanup:nil]) { + if ((self = [self initWithBytesNoCopy:copy.data() length:length owningBytes:YES cleanup:nil]) != nil) { _copiedBytes = std::move(copy); } return self; @@ -93,21 +99,6 @@ + (instancetype)arrayBufferWithUnownedBytes:(void *)bytes length:(NSUInteger)len #pragma mark - Accessors -- (void *)mutableBytes -{ - return _bytes; -} - -- (NSUInteger)length -{ - return _length; -} - -- (BOOL)isOwningBytes -{ - return _owningBytes; -} - - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; length = %lu; owningBytes = %@>", @@ -119,6 +110,7 @@ - (NSString *)description - (void)dealloc { + // Runs on whichever thread drops the last reference, so cleanup blocks must be thread-agnostic. if (_cleanup != nil) { _cleanup(); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.cpp index 241f032b9e6..b67a531c8e4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.cpp @@ -8,10 +8,8 @@ #include "JArrayBuffer.h" #include -#include #include #include -#include #include @@ -49,19 +47,6 @@ jboolean JArrayBuffer::isBytesValid() { return hasBytes() ? JNI_TRUE : JNI_FALSE; } -void JArrayBuffer::invalidate() noexcept { - if (!owningBytes_) { - buffer_.reset(); - } -} - -const std::shared_ptr& JArrayBuffer::mutableBuffer() const { - if (!hasBytes()) { - throw std::runtime_error(kRevokedBorrowMessage); - } - return buffer_; -} - jni::local_ref JArrayBuffer::create( jni::local_ref byteBuffer, std::shared_ptr buffer, @@ -72,22 +57,13 @@ jni::local_ref JArrayBuffer::create( return javaPart; } -jni::local_ref JArrayBuffer::createOwning( +jni::local_ref JArrayBuffer::createWithOwnedBytes( std::shared_ptr buffer) { auto byteBuffer = jni::JByteBuffer::wrapBytes(buffer->data(), buffer->size()); return create(std::move(byteBuffer), std::move(buffer), true); } -jni::local_ref JArrayBuffer::createUnowned( - void* bytes, - size_t size) { - auto byteBuffer = - jni::JByteBuffer::wrapBytes(static_cast(bytes), size); - auto buffer = std::make_shared(byteBuffer); - return create(std::move(byteBuffer), std::move(buffer), false); -} - -jni::local_ref JArrayBuffer::createOwned( +jni::local_ref JArrayBuffer::createWithCopiedBytes( const void* bytes, size_t size) { auto byteBuffer = jni::JByteBuffer::allocateDirect(static_cast(size)); @@ -100,6 +76,15 @@ jni::local_ref JArrayBuffer::createOwned( return create(std::move(byteBuffer), std::move(buffer), true); } +jni::local_ref JArrayBuffer::createWithUnownedBytes( + void* bytes, + size_t size) { + auto byteBuffer = + jni::JByteBuffer::wrapBytes(static_cast(bytes), size); + auto buffer = std::make_shared(byteBuffer); + return create(std::move(byteBuffer), std::move(buffer), false); +} + std::shared_ptr JArrayBuffer::toJSBuffer( jsi::Runtime& runtime, jni::alias_ref arrayBuffer) { @@ -115,15 +100,26 @@ std::shared_ptr JArrayBuffer::toJSBuffer( } const auto& buffer = self->mutableBuffer(); - if (self->owningBytes_) { + if (self->isOwningBytes()) { return buffer; } // Borrowed bytes still belong to the inbound JS ArrayBuffer; copy them before // handing a new buffer back to JS. - auto bytes = std::span(buffer->data(), buffer->size()); - return std::make_shared( - std::vector(bytes.begin(), bytes.end())); + return detail::copyToOwnedBuffer(buffer->data(), buffer->size()); +} + +const std::shared_ptr& JArrayBuffer::mutableBuffer() const { + if (!hasBytes()) { + throw std::runtime_error(kRevokedBorrowMessage); + } + return buffer_; +} + +void JArrayBuffer::invalidate() noexcept { + if (!owningBytes_) { + buffer_.reset(); + } } } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.h index 23029d35e75..8e34db0cb17 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBuffer.h @@ -28,15 +28,16 @@ class JArrayBuffer : public jni::HybridClass { // JS ArrayBuffer with a native MutableBuffer (tryGetMutableBuffer). Retain // the owner so the bytes stay valid after the call. - static jni::local_ref createOwning(std::shared_ptr buffer); - - // JS-heap bytes passed to a synchronous call. Zero-copy for the call only; - // do not retain the result. - static jni::local_ref createUnowned(void *bytes, size_t size); + static jni::local_ref createWithOwnedBytes(std::shared_ptr buffer); // Copy JS-heap bytes into a new owned buffer. Used for async/promise calls // and anywhere the module needs its own copy of the data. - static jni::local_ref createOwned(const void *bytes, size_t size); + static jni::local_ref createWithCopiedBytes(const void *bytes, size_t size); + + // JS-heap bytes passed to a synchronous call. Zero-copy for the call only; + // do not retain the result. No Kotlin equivalent by design: only the + // TurboModule bridge may lend out JS-heap bytes. + static jni::local_ref createWithUnownedBytes(void *bytes, size_t size); // Convert a module return value for rt.createArrayBuffer. Owning buffers pass // through; borrowed ones are copied because createArrayBuffer needs its own @@ -44,12 +45,6 @@ class JArrayBuffer : public jni::HybridClass { // its borrow has been revoked. static std::shared_ptr toJSBuffer(jsi::Runtime &runtime, jni::alias_ref arrayBuffer); - // Revokes access to borrowed bytes. Called when the call frame that lent the - // bytes unwinds, so a module that retained a non-owning ArrayBuffer gets an - // exception instead of reading memory the JS heap has moved or freed. Owning - // buffers are unaffected. - void invalidate() noexcept; - // The bytes this buffer was created over. Throws if a borrow has since been // revoked by invalidate(). const std::shared_ptr &mutableBuffer() const; @@ -66,6 +61,12 @@ class JArrayBuffer : public jni::HybridClass { return owningBytes_; } + // Revokes access to borrowed bytes. Called when the call frame that lent the + // bytes unwinds, so a module that retained a non-owning ArrayBuffer gets an + // exception instead of reading memory the JS heap has moved or freed. Owning + // buffers are unaffected. + void invalidate() noexcept; + JArrayBuffer(std::shared_ptr buffer, bool owningBytes) noexcept : buffer_(std::move(buffer)), owningBytes_(owningBytes) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/test/JArrayBufferTest.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/test/JArrayBufferTest.cpp index 8eebde8b2ec..55ff651700b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/test/JArrayBufferTest.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/test/JArrayBufferTest.cpp @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +#include #include #include @@ -19,22 +20,9 @@ namespace facebook::react { namespace { -class TestBuffer final : public jsi::MutableBuffer { - public: - explicit TestBuffer(std::vector bytes) noexcept - : bytes_(std::move(bytes)) {} - - size_t size() const override { - return bytes_.size(); - } - - uint8_t* data() override { - return bytes_.data(); - } - - private: - std::vector bytes_; -}; +std::shared_ptr makeBuffer(std::vector bytes) { + return std::make_shared(std::move(bytes)); +} } // namespace @@ -47,7 +35,7 @@ class TestBuffer final : public jsi::MutableBuffer { */ TEST(JArrayBufferTest, owningBufferExposesItsBytes) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); JArrayBuffer arrayBuffer{buffer, true}; EXPECT_TRUE(arrayBuffer.isOwningBytes()); @@ -55,7 +43,7 @@ TEST(JArrayBufferTest, owningBufferExposesItsBytes) { } TEST(JArrayBufferTest, borrowedBufferExposesItsBytesBeforeInvalidation) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); JArrayBuffer arrayBuffer{buffer, false}; EXPECT_FALSE(arrayBuffer.isOwningBytes()); @@ -69,7 +57,7 @@ TEST(JArrayBufferTest, borrowedBufferExposesItsBytesBeforeInvalidation) { * occupies that memory. */ TEST(JArrayBufferTest, invalidateRevokesABorrow) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); JArrayBuffer arrayBuffer{buffer, false}; arrayBuffer.invalidate(); @@ -78,7 +66,7 @@ TEST(JArrayBufferTest, invalidateRevokesABorrow) { } TEST(JArrayBufferTest, invalidateLeavesAnOwningBufferUsable) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); JArrayBuffer arrayBuffer{buffer, true}; arrayBuffer.invalidate(); @@ -87,7 +75,7 @@ TEST(JArrayBufferTest, invalidateLeavesAnOwningBufferUsable) { } TEST(JArrayBufferTest, invalidateIsIdempotent) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); JArrayBuffer arrayBuffer{buffer, false}; arrayBuffer.invalidate(); @@ -100,7 +88,7 @@ TEST(JArrayBufferTest, invalidateIsIdempotent) { // aliasing adapter (and the JNI global ref inside it) is torn down with the // call frame rather than at the whim of the Java GC. TEST(JArrayBufferTest, invalidateReleasesTheBorrowedBuffer) { - auto buffer = std::make_shared(std::vector{1, 2, 3}); + auto buffer = makeBuffer({1, 2, 3}); std::weak_ptr weakBuffer = buffer; JArrayBuffer arrayBuffer{std::move(buffer), false}; diff --git a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.cpp b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.cpp new file mode 100644 index 00000000000..c6671fdf8fd --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include + +namespace facebook::react::detail { + +void throwIfDetached( + jsi::Runtime& rt, + const jsi::ArrayBuffer& buffer, + const char* callerName) { + // Process-wide rather than per-runtime: every runtime in a process shares one + // engine build, so one rejection means none of them support the check. + static std::atomic unsupported{false}; + if (unsupported.load(std::memory_order_relaxed)) { + return; + } + + bool detached = false; + try { + detached = buffer.detached(rt); + } catch (const jsi::JSINativeException&) { + unsupported.store(true, std::memory_order_relaxed); + return; + } + if (detached) { + throw jsi::JSError( + rt, std::string(callerName) + ": ArrayBuffer is detached"); + } +} + +std::shared_ptr copyToOwnedBuffer( + const uint8_t* bytes, + size_t size) { + if (size == 0) { + return std::make_shared(std::vector{}); + } + auto span = std::span(bytes, size); + return std::make_shared( + std::vector(span.begin(), span.end())); +} + +} // namespace facebook::react::detail diff --git a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h index 8739fb2881c..8ff9a92a6c3 100644 --- a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h +++ b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h @@ -7,8 +7,8 @@ #pragma once -#include -#include +#include +#include #include @@ -40,6 +40,20 @@ class OwnedBytesBuffer final : public jsi::MutableBuffer { std::vector bytes_; }; +/** + * Best-effort detached check. jsi::ArrayBuffer::detached relies on the JS-level + * `detached` property, which some runtimes (e.g. Hermes) don't implement and + * signal by throwing. In that case we silently skip the check rather than + * surfacing a confusing error from an unrelated method, and remember that the + * runtime rejected it so later calls skip it for free. + */ +void throwIfDetached(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer, const char *callerName); + +/** + * Copies bytes into a new owning buffer. `bytes` may be null when `size` is 0. + */ +std::shared_ptr copyToOwnedBuffer(const uint8_t *bytes, size_t size); + } // namespace detail template <> @@ -77,7 +91,7 @@ class AsyncArrayBuffer { // Zero-copy if the input has a native MutableBuffer; copies otherwise. static AsyncArrayBuffer acquire(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) { - throwIfDetached(rt, buffer, "AsyncArrayBuffer::acquire"); + detail::throwIfDetached(rt, buffer, "AsyncArrayBuffer::acquire"); if (auto mutableBuf = buffer.tryGetMutableBuffer(rt)) { return AsyncArrayBuffer{std::move(mutableBuf)}; } @@ -87,7 +101,7 @@ class AsyncArrayBuffer { // Zero-copy. Throws if the input has no native MutableBuffer. static AsyncArrayBuffer borrow(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) { - throwIfDetached(rt, buffer, "AsyncArrayBuffer::borrow"); + detail::throwIfDetached(rt, buffer, "AsyncArrayBuffer::borrow"); auto mutableBuf = buffer.tryGetMutableBuffer(rt); if (!mutableBuf) { throw jsi::JSError( @@ -101,7 +115,7 @@ class AsyncArrayBuffer { // Always copies. static AsyncArrayBuffer copy(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) { - throwIfDetached(rt, buffer, "AsyncArrayBuffer::copy"); + detail::throwIfDetached(rt, buffer, "AsyncArrayBuffer::copy"); return copyBytes(rt, buffer); } @@ -140,30 +154,12 @@ class AsyncArrayBuffer { return buffer_; } - // Best-effort detached check. jsi::ArrayBuffer::detached relies on the JS-level - // `detached` property, which some runtimes (e.g. Hermes) don't implement and - // signal by throwing. In that case we silently skip the check rather than - // surfacing a confusing error from an unrelated method. - static void throwIfDetached(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer, const char *callerName) - { - bool detached = false; - try { - detached = buffer.detached(rt); - } catch (const jsi::JSINativeException &) { - return; - } - if (detached) { - throw jsi::JSError(rt, std::string(callerName) + ": ArrayBuffer is detached"); - } - } - private: explicit AsyncArrayBuffer(std::shared_ptr buffer) noexcept : buffer_{std::move(buffer)} {} static AsyncArrayBuffer copyBytes(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) { - auto bytes = std::span(buffer.data(rt), buffer.size(rt)); - return wrap(std::vector(bytes.begin(), bytes.end())); + return AsyncArrayBuffer{detail::copyToOwnedBuffer(buffer.data(rt), buffer.size(rt))}; } std::shared_ptr buffer_; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm b/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm index 0000965280c..ce1e19463e6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/iostests/RCTTurboModuleTests.mm @@ -11,6 +11,7 @@ #import #import #import +#import #import #import @@ -32,23 +33,6 @@ @implementation RCTTestTurboModule @end -// Minimal concrete MutableBuffer that owns its bytes, used to observe lifetime. -class TestMutableBuffer : public facebook::jsi::MutableBuffer { - public: - explicit TestMutableBuffer(size_t size) : bytes_(size, 0) {} - size_t size() const override - { - return bytes_.size(); - } - uint8_t *data() override - { - return bytes_.data(); - } - - private: - std::vector bytes_; -}; - // `jsi::Runtime::tryGetMutableBuffer` is optional — the Hermes branch linked into apps returns // nullptr for every ArrayBuffer — so decorate the runtime to answer it for the buffers created // through it. That keeps the coverage of the native-backed path independent of the engine. @@ -168,7 +152,7 @@ - (void)testNativeBackedArrayBufferIsAliasedAndKeepsBackingStoreAlive auto hermesRuntime = facebook::hermes::makeHermesRuntime(); MutableBufferAwareRuntime runtime(*hermesRuntime); - auto buffer = std::make_shared(kBufferSize); + auto buffer = std::make_shared(std::vector(kBufferSize, 0)); *buffer->data() = 0xAB; const uint8_t *sourceBytes = buffer->data(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index 0db4927a633..12c4975c805 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -466,7 +468,7 @@ JNIArgs convertJSIArgsToJNIArgs( } auto arrayBuffer = arg->getObject(rt).getArrayBuffer(rt); - AsyncArrayBuffer::throwIfDetached( + detail::throwIfDetached( rt, arrayBuffer, "JavaTurboModule::convertJSIArgsToJNIArgs"); auto size = arrayBuffer.size(rt); @@ -476,16 +478,21 @@ JNIArgs convertJSIArgsToJNIArgs( "JavaTurboModule::convertJSIArgsToJNIArgs: ArrayBuffer exceeds maximum size."); } - // Runtimes without a native buffer for this ArrayBuffer return nullptr, - // but the Static Hermes tracing runtime throws instead, and argument - // conversion runs outside any std::exception handler. Treat a failed - // probe as "no native buffer" so a traced session copies the bytes rather - // than aborting the process. + // Runtimes without a native buffer return nullptr, but the Static Hermes + // tracing runtime signals that by throwing std::logic_error. Treat a + // failed probe as "no native buffer" so a traced session copies rather + // than aborting; anything else is a real failure and propagates. std::shared_ptr mutableBuffer; try { mutableBuffer = arrayBuffer.tryGetMutableBuffer(rt); - } catch (const std::exception&) { - mutableBuffer = nullptr; + } catch (const std::logic_error& e) { + static std::once_flag warnOnce; + std::call_once(warnOnce, [&] { + LOG(WARNING) + << "JavaTurboModule::convertJSIArgsToJNIArgs: tryGetMutableBuffer is unsupported by this runtime (" + << e.what() + << "); ArrayBuffer arguments will be copied instead of aliased"; + }); } bool borrowsJSBytes = false; @@ -493,18 +500,21 @@ JNIArgs convertJSIArgsToJNIArgs( // Backed by a native buffer: alias it and retain its owner, so the // bytes stay valid for as long as the module holds the ArrayBuffer. if (mutableBuffer) { - return JArrayBuffer::createOwning(std::move(mutableBuffer)); + return JArrayBuffer::createWithOwnedBytes(std::move(mutableBuffer)); } // JS heap bytes on a synchronous call: lend them for the duration of - // the call. + // the call. Unlike iOS, which copies whenever the method signature + // exposes a block, a module that retains the buffer here gets an + // IllegalStateException from the revocation below. if (isSyncInvocation) { borrowsJSBytes = true; - return JArrayBuffer::createUnowned(arrayBuffer.data(rt), size); + return JArrayBuffer::createWithUnownedBytes( + arrayBuffer.data(rt), size); } // JS heap bytes that outlive the call: copy. - return JArrayBuffer::createOwned(arrayBuffer.data(rt), size); + return JArrayBuffer::createWithCopiedBytes(arrayBuffer.data(rt), size); }(); if (borrowsJSBytes) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index 7270187b414..165226e3952 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -210,11 +210,29 @@ size_t size() const override }; } +// A block parameter means the module can hand an argument to a callback it invokes after the call +// returns, so nothing passed to such a method may alias the JS heap. +static BOOL methodSignatureTakesBlock(NSMethodSignature *methodSignature) +{ + const char *BLOCK_TYPE = @encode( + __typeof__(^{ + })); + + for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { + const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i]; + if (objCArgType == BLOCK_TYPE) { + return YES; + } + } + return NO; +} + // Native-backed buffers are aliased and keep their backing store alive, so they stay valid for // as long as the module holds them. JS-heap buffers have nothing to retain — their bytes are // freed when the ArrayBuffer is collected or detached — so `mustCopyBytes` is set whenever the -// invocation may outlive the JS call, and they are aliased only when it cannot. Engines that -// don't hand out the backing MutableBuffer fall back to those JS-heap rules for every buffer. +// invocation may outlive the JS call (an async return kind, or a block parameter the module can +// call later), and they are aliased only when it cannot. Engines that don't hand out the backing +// MutableBuffer fall back to those JS-heap rules for every buffer. static RCTArrayBuffer * convertJSIArrayBufferToRCTArrayBuffer(jsi::Runtime &rt, const jsi::ArrayBuffer &arrayBuffer, BOOL mustCopyBytes) { @@ -813,10 +831,15 @@ TraceSection s( NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature]; [inv setSelector:selector]; + // A block argument can outlive the call, and JS drains it asynchronously, so an ArrayBuffer the + // module hands to it must own its bytes even when the method itself returns synchronously. + BOOL mustCopyArrayBufferBytes = mustCopyBytes || methodSignatureTakesBlock(methodSignature); + for (size_t i = 0; i < count; i++) { const jsi::Value &arg = args[i]; const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2]; - setInvocationArg(runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation, mustCopyBytes); + setInvocationArg( + runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation, mustCopyArrayBufferBytes); } if (isSync) { diff --git a/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm index 14f32b48e84..939f1a2bc8d 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm +++ b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm @@ -14,6 +14,7 @@ #import #import +#import #import #import @@ -142,6 +143,15 @@ - (void)testMethodWhichStoresArrayBuffer:(RCTArrayBuffer *)payload self.lastReceivedPayload = [NSData dataWithBytes:payload.mutableBytes length:payload.length]; } +- (NSNumber *)testMethodWhichPassesArrayBufferToCallback:(RCTArrayBuffer *)buffer + callback:(RCTResponseSenderBlock)callback +{ + // The buffer escapes into a callback JS drains after this method returns, so the framework must + // have handed us a copy rather than an alias of the JS heap. + callback(@[ buffer ]); + return @(buffer.isOwningBytes); +} + - (void)testMethodWhichCallsBackWithArrayBuffer:(double)size callback:(RCTResponseSenderBlock)callback { callback(@[ createIntegerSequenceBuffer(static_cast(size)) ]); @@ -444,6 +454,69 @@ - (void)testCallbackDeliversArrayBuffer XCTAssertEqual(callbackBytes[2], 2); } +// A sync method that hands its argument to a callback lets the buffer escape the call, so the +// argument must be copied even though the return kind would otherwise allow aliasing. +- (void)testArrayBufferIsCopiedWhenAMethodTakesACallback +{ + auto hermesRuntime = createHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto jsInvoker = std::make_shared(*rt); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = jsInvoker, + .nativeMethodCallInvoker = std::make_shared(), + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + auto sourceBuffer = rt->global() + .getPropertyAsFunction(*rt, "eval") + .call(*rt, "new Uint8Array([1, 2, 3]).buffer") + .asObject(*rt) + .getArrayBuffer(*rt); + + std::vector callbackBytes; + auto onCallback = facebook::jsi::Function::createFromHostFunction( + *rt, + facebook::jsi::PropNameID::forAscii(*rt, "onCallback"), + 1, + [&callbackBytes]( + facebook::jsi::Runtime &runtime, + const facebook::jsi::Value &, + const facebook::jsi::Value *callbackArgs, + size_t count) -> facebook::jsi::Value { + if (count == 1) { + const auto &callbackArg = *callbackArgs; + if (callbackArg.isObject() && callbackArg.asObject(runtime).isArrayBuffer(runtime)) { + callbackBytes = bytesFromArrayBuffer(runtime, callbackArg.asObject(runtime).getArrayBuffer(runtime)); + } + } + return facebook::jsi::Value::undefined(); + }); + std::array args = { + facebook::jsi::Value(*rt, sourceBuffer), facebook::jsi::Value(*rt, onCallback)}; + + auto result = module.invokeObjCMethod( + *rt, + BooleanKind, + "testMethodWhichPassesArrayBufferToCallback", + @selector(testMethodWhichPassesArrayBufferToCallback:callback:), + args.data(), + 2); + + XCTAssertTrue(result.getBool(), @"A buffer that can escape into a callback must own its bytes"); + + jsInvoker->flushQueue(); + + XCTAssertEqual(callbackBytes.size(), 3u); + XCTAssertEqual(callbackBytes[0], 1); + XCTAssertEqual(callbackBytes[1], 2); + XCTAssertEqual(callbackBytes[2], 3); +} + - (void)testPromiseResolvesArrayBuffer { auto hermesRuntime = createHermesRuntime(true); @@ -498,4 +571,22 @@ - (void)testPromiseResolvesArrayBuffer XCTAssertEqual(resolvedBytes[3], 3); } +// +arrayBufferWithOwnedBytes:length:cleanup: takes ownership of `bytes` via `cleanup`, so raising +// without running the block leaks the caller's allocation. +- (void)testOwnedBytesCleanupRunsBeforeRaisingOnNullBytes +{ + __block BOOL cleanupRan = NO; + + XCTAssertThrowsSpecificNamed( + [RCTArrayBuffer arrayBufferWithOwnedBytes:NULL + length:4 + cleanup:^{ + cleanupRan = YES; + }], + NSException, + NSInvalidArgumentException, + @"NULL bytes with a non-zero length must raise"); + XCTAssertTrue(cleanupRan, @"The cleanup block must run before the initializer raises, or its bytes leak"); +} + @end diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 828513e24e8..f8703b5c07d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1652,7 +1652,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -2799,9 +2798,9 @@ class facebook::react::JArrayBuffer : public jni::HybridClass& mutableBuffer() const; public static constexpr auto kJavaDescriptor; - public static jni::local_ref createOwned(const void* bytes, size_t size); - public static jni::local_ref createOwning(std::shared_ptr buffer); - public static jni::local_ref createUnowned(void* bytes, size_t size); + public static jni::local_ref createWithCopiedBytes(const void* bytes, size_t size); + public static jni::local_ref createWithOwnedBytes(std::shared_ptr buffer); + public static jni::local_ref createWithUnownedBytes(void* bytes, size_t size); public static std::shared_ptr toJSBuffer(facebook::jsi::Runtime& runtime, jni::alias_ref arrayBuffer); public static void registerNatives(); public void invalidate() noexcept; @@ -9979,6 +9978,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -10001,6 +10001,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 39e6d87b0d0..e2a5e9fc0aa 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1646,7 +1646,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -2757,9 +2756,9 @@ class facebook::react::JArrayBuffer : public jni::HybridClass& mutableBuffer() const; public static constexpr auto kJavaDescriptor; - public static jni::local_ref createOwned(const void* bytes, size_t size); - public static jni::local_ref createOwning(std::shared_ptr buffer); - public static jni::local_ref createUnowned(void* bytes, size_t size); + public static jni::local_ref createWithCopiedBytes(const void* bytes, size_t size); + public static jni::local_ref createWithOwnedBytes(std::shared_ptr buffer); + public static jni::local_ref createWithUnownedBytes(void* bytes, size_t size); public static std::shared_ptr toJSBuffer(facebook::jsi::Runtime& runtime, jni::alias_ref arrayBuffer); public static void registerNatives(); public void invalidate() noexcept; @@ -9602,6 +9601,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -9624,6 +9624,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index f5dcbb21b54..c8428606af1 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1650,7 +1650,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -2796,9 +2795,9 @@ class facebook::react::JArrayBuffer : public jni::HybridClass& mutableBuffer() const; public static constexpr auto kJavaDescriptor; - public static jni::local_ref createOwned(const void* bytes, size_t size); - public static jni::local_ref createOwning(std::shared_ptr buffer); - public static jni::local_ref createUnowned(void* bytes, size_t size); + public static jni::local_ref createWithCopiedBytes(const void* bytes, size_t size); + public static jni::local_ref createWithOwnedBytes(std::shared_ptr buffer); + public static jni::local_ref createWithUnownedBytes(void* bytes, size_t size); public static std::shared_ptr toJSBuffer(facebook::jsi::Runtime& runtime, jni::alias_ref arrayBuffer); public static void registerNatives(); public void invalidate() noexcept; @@ -9832,6 +9831,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -9854,6 +9854,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index bfbe9280ffc..923dd69248e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4254,7 +4254,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -11868,6 +11867,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -11890,6 +11890,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 5a097a3af56..32575d277f4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4241,7 +4241,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -11557,6 +11556,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -11579,6 +11579,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 8a50f499d13..9ed188e71b7 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4252,7 +4252,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -11731,6 +11730,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -11753,6 +11753,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index e28f3bd7028..1b670c3d9ba 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -976,7 +976,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -7027,6 +7026,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -7049,6 +7049,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 1c421afcdf7..631a01001ad 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -971,7 +971,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -6852,6 +6851,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -6874,6 +6874,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 2b46b26d7b0..f06de010c59 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -974,7 +974,6 @@ class facebook::react::AsyncArrayBuffer { public static facebook::react::AsyncArrayBuffer copy(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); public static facebook::react::AsyncArrayBuffer wrap(std::shared_ptr buffer) noexcept; public static facebook::react::AsyncArrayBuffer wrap(std::vector bytes); - public static void throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); public std::shared_ptr getMutableBuffer() const noexcept; public uint8_t* data() noexcept; public ~AsyncArrayBuffer() = default; @@ -7018,6 +7017,7 @@ constexpr uint8_t facebook::react::detail::clampAlpha(std::optional alpha constexpr uint8_t facebook::react::detail::hexToNumeric(std::string_view hex, facebook::react::detail::HexColorType hexType); float facebook::react::detail::normalizeHue(float hue); std::optional facebook::react::detail::normalizeHueComponent(const std::variant& component); +std::shared_ptr facebook::react::detail::copyToOwnedBuffer(const uint8_t* bytes, size_t size); std::tuple facebook::react::detail::hslToRgb(float h, float s, float l); std::tuple facebook::react::detail::hwbToRgb(float h, float w, float b); template @@ -7040,6 +7040,7 @@ template C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); +void facebook::react::detail::throwIfDetached(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer, const char* callerName); class facebook::react::detail::OwnedBytesBuffer : public facebook::jsi::MutableBuffer { public OwnedBytesBuffer(std::vector bytes) noexcept;