Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ function getParamObjCType(
return notStruct(wrapOptional('NSArray *', !nullable));
}
case 'ArrayBufferTypeAnnotation': {
return notStruct(wrapOptional('NSMutableData *', !nullable));
return notStruct(wrapOptional('NSData *', !nullable));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ Map {
@protocol NativeSampleTurboModuleSpec <RCTBridgeModule, RCTTurboModule>

- (NSMutableData *)getArrayBuffer;
- (void)voidArrayBuffer:(NSMutableData *)arg;
- (void)voidNullableArrayBuffer:(NSMutableData * _Nullable)arg;
- (void)voidArrayBuffer:(NSData *)arg;
- (void)voidNullableArrayBuffer:(NSData * _Nullable)arg;
- (void)promiseArrayBuffer:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,11 @@ - (void)testInvokeTurboModuleWithNull
OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:nil]);
}

// A JS ArrayBuffer converts to an NSMutableData that owns an independent copy of
// the bytes — NSMutableData cannot alias a foreign buffer, so the result stays
// valid and is safe to mutate after the source buffer is gone. This covers the
// ArrayBuffer-backed-by-native-MutableBuffer case, which is the one that could in
// principle have been aliased zero-copy.
- (void)testArrayBufferConvertsToIndependentNSMutableData
// A JS ArrayBuffer converts (on the argument path) to an immutable NSData that
// owns an independent copy of the bytes, so the result stays valid after the
// source buffer is gone. This covers the ArrayBuffer-backed-by-native-MutableBuffer
// case, which is the one that could in principle have been aliased zero-copy.
- (void)testArrayBufferConvertsToIndependentNSData
{
constexpr size_t kBufferSize = 64 * 1024;

Expand All @@ -142,15 +141,15 @@ - (void)testArrayBufferConvertsToIndependentNSMutableData
id converted =
TurboModuleConvertUtils::convertJSIValueToObjCObject(*rt, facebook::jsi::Value(*rt, arrayBuffer), nullptr);

XCTAssertTrue([converted isKindOfClass:[NSMutableData class]]);
NSMutableData *data = (NSMutableData *)converted;
XCTAssertTrue([converted isKindOfClass:[NSData class]]);
NSData *data = (NSData *)converted;
XCTAssertEqual(data.length, (NSUInteger)kBufferSize);
XCTAssertEqual(*static_cast<const uint8_t *>(data.bytes), 0xAB);

// Independent copy: mutating the NSMutableData must not write through to the
// source MutableBuffer.
*static_cast<uint8_t *>(data.mutableBytes) = 0xCD;
XCTAssertEqual(*buffer->data(), 0xAB, @"NSMutableData must not alias the source buffer");
// Independent copy: mutating the source MutableBuffer must not write through to
// the NSData.
*buffer->data() = 0xCD;
XCTAssertEqual(*static_cast<const uint8_t *>(data.bytes), 0xAB, @"NSData must not alias the source buffer");
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,15 @@ size_t size() const override
};
}

// Copy the ArrayBuffer's bytes into an NSMutableData. A zero-copy wrap is not
// possible here: NSMutableData needs its own resizable backing store and cannot
// alias a foreign buffer (even via initWithBytesNoCopy:length:deallocator:, which
// copies eagerly for the mutable subclass). Copying also makes the result safe to
// retain in a block, store, or dispatch to another thread, regardless of whether
// the bytes were owned by JS (valid only for this callstack) or by a native
// MutableBuffer (which the JS ArrayBuffer may GC concurrently).
static NSMutableData *convertJSIArrayBufferToNSMutableData(jsi::Runtime &rt, const jsi::ArrayBuffer &value)
// Copy the ArrayBuffer's bytes into an immutable NSData. An inbound buffer is
// owned by the caller, not the native module, so NSData (not NSMutableData) is
// the correct read-only contract. Copying makes the NSData self-contained and
// safe to retain in a block, store, or dispatch to another thread, regardless of
// whether the bytes were owned by JS (valid only for this callstack) or by a
// native MutableBuffer (which the JS ArrayBuffer may GC concurrently).
static NSData *convertJSIArrayBufferToNSData(jsi::Runtime &rt, const jsi::ArrayBuffer &value)
{
return [NSMutableData dataWithBytes:value.data(rt) length:value.size(rt)];
return [NSData dataWithBytes:value.data(rt) length:value.size(rt)];
}

id convertJSIValueToObjCObject(
Expand Down Expand Up @@ -241,7 +240,7 @@ id convertJSIValueToObjCObject(
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
if (o.isArrayBuffer(runtime)) {
return convertJSIArrayBufferToNSMutableData(runtime, o.getArrayBuffer(runtime));
return convertJSIArrayBufferToNSData(runtime, o.getArrayBuffer(runtime));
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,17 @@ @implementation RCTTestArrayBufferTurboModule

RCT_EXPORT_MODULE()

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, testMethodWhichMutatesArrayBuffer : (NSMutableData *)buffer)
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, testMethodWhichTransformsArrayBuffer : (NSData *)buffer)
{
auto *bytes = static_cast<uint8_t *>(buffer.mutableBytes);
for (NSUInteger i = 0; i < buffer.length; ++i) {
NSMutableData *result = [buffer mutableCopy];
auto *bytes = static_cast<uint8_t *>(result.mutableBytes);
for (NSUInteger i = 0; i < result.length; ++i) {
bytes[i] = static_cast<uint8_t>((i + 1) * 10);
}
return buffer;
return result;
}

RCT_EXPORT_METHOD(testMethodWhichStoresArrayBuffer : (NSMutableData *)payload)
RCT_EXPORT_METHOD(testMethodWhichStoresArrayBuffer : (NSData *)payload)
{
self.lastReceivedPayload = [payload copy];
}
Expand Down Expand Up @@ -169,8 +170,8 @@ - (void)testSyncArrayBufferRoundTrip
auto result = module.invokeObjCMethod(
*rt,
ArrayBufferKind,
"testMethodWhichMutatesArrayBuffer",
@selector(testMethodWhichMutatesArrayBuffer:),
"testMethodWhichTransformsArrayBuffer",
@selector(testMethodWhichTransformsArrayBuffer:),
args,
1);

Expand Down
Loading