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 @@ -273,8 +273,8 @@ function translateFunctionParamToJavaType(
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
case 'ArrayBufferTypeAnnotation':
imports.add('java.nio.ByteBuffer');
return wrapOptional('ByteBuffer', isRequired);
imports.add('com.facebook.react.bridge.ArrayBuffer');
return wrapOptional('ArrayBuffer', isRequired);
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(createErrorMessage(realTypeAnnotation.type));
Expand Down Expand Up @@ -370,8 +370,8 @@ function translateFunctionReturnTypeToJavaType(
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
case 'ArrayBufferTypeAnnotation':
imports.add('java.nio.ByteBuffer');
return wrapOptional('ByteBuffer', isRequired);
imports.add('com.facebook.react.bridge.ArrayBuffer');
return wrapOptional('ArrayBuffer', isRequired);
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(createErrorMessage(realTypeAnnotation.type));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ function translateParamTypeToJniType(
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
case 'ArrayBufferTypeAnnotation':
return 'Ljava/nio/ByteBuffer;';
return 'Lcom/facebook/react/bridge/ArrayBuffer;';
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(
Expand Down Expand Up @@ -397,7 +397,7 @@ function translateReturnTypeToJniType(
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
case 'ArrayBufferTypeAnnotation':
return 'Ljava/nio/ByteBuffer;';
return 'Lcom/facebook/react/bridge/ArrayBuffer;';
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ Map {
package com.facebook.fbreact.specs;

import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ArrayBuffer;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

Expand All @@ -80,15 +80,15 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo

@ReactMethod(isBlockingSynchronousMethod = true)
@DoNotStrip
public abstract ByteBuffer getArrayBuffer();
public abstract ArrayBuffer getArrayBuffer();

@ReactMethod
@DoNotStrip
public abstract void voidArrayBuffer(ByteBuffer arg);
public abstract void voidArrayBuffer(ArrayBuffer arg);

@ReactMethod
@DoNotStrip
public abstract void voidNullableArrayBuffer(@Nullable ByteBuffer arg);
public abstract void voidNullableArrayBuffer(@Nullable ArrayBuffer arg);
}
",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ namespace facebook::react {

static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Ljava/nio/ByteBuffer;\\", args, count, cachedMethodId);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Lcom/facebook/react/bridge/ArrayBuffer;\\", args, count, cachedMethodId);
}

static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId);
}

static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId);
}

NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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.
*/

package com.facebook.react.bridge

import com.facebook.jni.HybridClassBase
import com.facebook.proguard.annotations.DoNotStrip
import java.nio.ByteBuffer

/**
* A fixed-length byte buffer for TurboModule `ArrayBuffer` arguments and return values.
*
* @property isOwningBytes:
* - `true` — safe to retain and return to JS. Synchronize externally if JS may
* touch the same memory concurrently.
* - `false` — bytes are borrowed from a JS `ArrayBuffer` for the current
* synchronous call only. Copy with [arrayBufferWithCopiedBytes] to keep them.
*/
@DoNotStrip
public class ArrayBuffer : HybridClassBase {

private val buffer: ByteBuffer

/** Whether this buffer owns its bytes. See the class documentation. */
public val isOwningBytes: Boolean

@DoNotStrip
private constructor(buffer: ByteBuffer, isOwningBytes: Boolean) : super() {
this.buffer = buffer
this.isOwningBytes = isOwningBytes
}

/** @param size number of zero-filled bytes to allocate */
public constructor(size: Int) : this(allocateDirect(size), true) {
initHybrid(buffer)
}

public val bytes: ByteBuffer
get() = buffer

public val size: Int
get() = buffer.capacity()

private external fun initHybrid(buffer: ByteBuffer)

public companion object {
init {
ReactNativeJniCommonSoLoader.staticInit()
}

/** @param size number of zero-filled bytes to allocate. Same as `ArrayBuffer(size)`. */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithLength(size: Int): ArrayBuffer = ArrayBuffer(size)

/** @param bytes copied into a new owning buffer */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(bytes: ByteArray): ArrayBuffer {
val buffer = ArrayBuffer(bytes.size)
if (bytes.isNotEmpty()) {
buffer.bytes.put(bytes)
buffer.bytes.rewind()
}
return buffer
}

/** @param source remaining bytes are copied into a new owning buffer */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(source: ByteBuffer): ArrayBuffer {
val length = source.remaining()
val buffer = ArrayBuffer(length)
if (length > 0) {
buffer.bytes.put(source.duplicate())
buffer.bytes.rewind()
}
return buffer
}

/**
* @param source copied into a new owning buffer. Use to keep bytes from a non-owning argument
* after the call returns.
*/
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(source: ArrayBuffer): ArrayBuffer {
val length = source.size
val buffer = ArrayBuffer(length)
if (length > 0) {
val src = source.bytes.duplicate()
src.position(0)
src.limit(length)
buffer.bytes.put(src)
buffer.bytes.rewind()
}
return buffer
}

/**
* @param buffer direct [ByteBuffer] to alias without copying. The caller must keep it valid for
* as long as this [ArrayBuffer] lives.
*/
@JvmStatic
@DoNotStrip
public fun arrayBufferWithOwnedBytes(buffer: ByteBuffer): ArrayBuffer {
require(buffer.isDirect) { "arrayBufferWithOwnedBytes requires a direct ByteBuffer" }
return ArrayBuffer(buffer, true).apply { initHybrid(buffer) }
}

private fun allocateDirect(size: Int): ByteBuffer {
require(size >= 0) { "ArrayBuffer size must not be negative, got $size" }
return ByteBuffer.allocateDirect(size)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-ut
add_library(
reactnativejni_common
OBJECT
JArrayBuffer.cpp
JDynamicNative.cpp
JReactMarker.cpp
NativeArray.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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 "JArrayBuffer.h"

#include <cstring>
#include <utility>
#include <vector>

#include "JByteBufferMutableBuffer.h"

namespace facebook::react {

namespace {

// Holds a copy of bytes borrowed from a JS ArrayBuffer.
class OwnedBytesBuffer final : public jsi::MutableBuffer {
public:
explicit OwnedBytesBuffer(std::vector<uint8_t> bytes) noexcept : bytes_(std::move(bytes)) {}

size_t size() const override
{
return bytes_.size();
}

uint8_t *data() override
{
return bytes_.data();
}

private:
std::vector<uint8_t> bytes_;
};

} // namespace

void JArrayBuffer::registerNatives()
{
registerHybrid({
makeNativeMethod("initHybrid", JArrayBuffer::initHybrid),
});
}

void JArrayBuffer::initHybrid(
jni::alias_ref<jhybridobject> jobj,
jni::alias_ref<jni::JByteBuffer> buffer)
{
setCxxInstance(jobj, std::make_shared<JByteBufferMutableBuffer>(buffer), true);
}

jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::create(
jni::local_ref<jni::JByteBuffer> byteBuffer,
std::shared_ptr<jsi::MutableBuffer> buffer,
bool owningBytes)
{
auto cxxPart = std::make_unique<JArrayBuffer>(std::move(buffer), owningBytes);
auto javaPart = newObjectJavaArgs(byteBuffer, owningBytes);
setNativePointer(javaPart, std::move(cxxPart));
return javaPart;
}

jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createOwning(
std::shared_ptr<jsi::MutableBuffer> buffer)
{
// NewDirectByteBuffer rejects a null address, which is what an empty
// jsi::ArrayBuffer reports, so empty buffers get an allocation of their own.
if (buffer->size() == 0) {
return create(jni::JByteBuffer::allocateDirect(0), std::move(buffer), true);
}

auto byteBuffer = jni::JByteBuffer::wrapBytes(buffer->data(), buffer->size());
return create(std::move(byteBuffer), std::move(buffer), true);
}

jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createUnowned(void *bytes, size_t size)
{
if (size == 0) {
return createOwned(nullptr, 0);
}

auto byteBuffer = jni::JByteBuffer::wrapBytes(static_cast<uint8_t *>(bytes), size);
auto buffer = std::make_shared<JByteBufferMutableBuffer>(byteBuffer);
return create(std::move(byteBuffer), std::move(buffer), false);
}

jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createOwned(const void *bytes, size_t size)
{
auto byteBuffer = jni::JByteBuffer::allocateDirect(static_cast<jint>(size));
if (size > 0 && bytes != nullptr) {
// @lint-ignore CLANGSECURITY facebook-security-vulnerable-memcpy
std::memcpy(byteBuffer->getDirectBytes(), bytes, size);
}

auto buffer = std::make_shared<JByteBufferMutableBuffer>(byteBuffer);
return create(std::move(byteBuffer), std::move(buffer), true);
}

std::shared_ptr<jsi::MutableBuffer> JArrayBuffer::toJSBuffer(jni::alias_ref<javaobject> arrayBuffer)
{
auto *self = arrayBuffer->cthis();
if (self->owningBytes_) {
return self->buffer_;
}

// Borrowed bytes still belong to the inbound JS ArrayBuffer; copy them before
// handing a new buffer back to JS.
auto *data = self->buffer_->data();
auto size = self->buffer_->size();
return std::make_shared<OwnedBytesBuffer>(std::vector<uint8_t>(data, data + size));
}

} // namespace facebook::react
Loading
Loading