From 780e257dc24ff96ddf9637aa72bb6293644850db Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 18 Aug 2026 20:26:12 -0700 Subject: [PATCH 1/2] Copy ArrayBuffer arguments when an ObjC TurboModule method takes a block (#57983) Summary: A JS-heap `ArrayBuffer` argument was aliased, not copied, whenever the method's return kind let the call complete synchronously. That is unsafe if the method also takes a block: the module can hand the buffer to the callback, and JS drains it after the call has returned, by which point the bytes may be gone. The copy decision now also looks at the ObjC method signature, and copies whenever a parameter is a block. Changelog: [iOS][Fixed] - Copy ArrayBuffer arguments in ObjC TurboModules when the method also takes a callback Differential Revision: D115767439 --- .../react-native/React/Base/RCTArrayBuffer.h | 6 +- .../ios/ReactCommon/RCTTurboModule.mm | 29 +++++++- .../RCTTurboModuleArrayBufferTests.mm | 73 +++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) 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/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..951668d45d2 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); From 2fcb293924da3ec41f88ffb9206981e79f8ecb6a Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 18 Aug 2026 20:26:12 -0700 Subject: [PATCH 2/2] Deduplicate the ArrayBuffer helpers and align the ObjC, C++ and JNI implementations (#58004) Summary: Follow-up cleanup on the `ArrayBuffer` TurboModule plumbing. No behaviour change except the leak fix below. - Move `detail::throwIfDetached` out of the header into a new `react/bridging/ArrayBuffer.cpp`, and drop the `AsyncArrayBuffer::throwIfDetached` wrapper that only forwarded to it. The JNI path now calls `detail::throwIfDetached` directly instead of going through `AsyncArrayBuffer`. - Add `detail::copyToOwnedBuffer`, so the C++ bridging path and the tests build an owning buffer the same way instead of each hand-rolling a `std::vector` copy. The header no longer needs ``/``. - Rename the `JArrayBuffer` factories to say what they do about ownership: `createOwning`/`createOwned`/`createUnowned` become `createWithOwnedBytes`/`createWithCopiedBytes`/`createWithUnownedBytes`, matching the `RCTArrayBuffer` naming. Move `invalidate()` next to the other private members. - `RCTArrayBuffer` uses `synthesize` rather than three hand-written accessors, and normalizes `mutableBytes` to NULL for a zero-length buffer so the documented "NULL exactly when empty" invariant holds for every factory. - Run the caller's `cleanup` block before the designated initializer raises on a NULL/non-zero-length mismatch. Nothing else would ever release those bytes, so raising first leaked them. - Treat only `std::logic_error` from `tryGetMutableBuffer` as "this runtime has no native buffer" on the JNI path, and log it once, instead of swallowing every `std::exception`. - Keep the macOS mirror of `RCTArrayBuffer` byte-identical to the iOS one. Changelog: [General][Changed] - Rename the internal JArrayBuffer factories and deduplicate the ArrayBuffer helpers Differential Revision: D116358593 --- .../react-native/React/Base/RCTArrayBuffer.mm | 34 +++++------ .../src/main/jni/react/jni/JArrayBuffer.cpp | 56 +++++++++---------- .../src/main/jni/react/jni/JArrayBuffer.h | 25 +++++---- .../jni/react/jni/test/JArrayBufferTest.cpp | 32 ++++------- .../react/bridging/ArrayBuffer.cpp | 51 +++++++++++++++++ .../ReactCommon/react/bridging/ArrayBuffer.h | 44 +++++++-------- .../core/iostests/RCTTurboModuleTests.mm | 20 +------ .../android/ReactCommon/JavaTurboModule.cpp | 34 +++++++---- .../RCTTurboModuleArrayBufferTests.mm | 18 ++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 9 +-- .../api-snapshots/ReactAndroidNewarchCxx.api | 9 +-- .../api-snapshots/ReactAndroidReleaseCxx.api | 9 +-- .../api-snapshots/ReactAppleDebugCxx.api | 3 +- .../api-snapshots/ReactAppleNewarchCxx.api | 3 +- .../api-snapshots/ReactAppleReleaseCxx.api | 3 +- .../api-snapshots/ReactCommonDebugCxx.api | 3 +- .../api-snapshots/ReactCommonNewarchCxx.api | 3 +- .../api-snapshots/ReactCommonReleaseCxx.api | 3 +- 18 files changed, 202 insertions(+), 157 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/bridging/ArrayBuffer.cpp 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/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm index 951668d45d2..939f1a2bc8d 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm +++ b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm @@ -571,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;