Skip to content

Commit 1a567c3

Browse files
authored
fix: fix exception handling in C++ native code (#244)
## Description Fix exception handling in C++ native code so that it results in JS promise getting rejected with an appropriate message. There are two fixes here: 1. Runtime type information in the current version of React Native is disabled because one of its dependencies, which results in C++ not being able to recognize that `std::runtime_error` is inheriting from `std::exception` so to extract the exception message we need to match the type exactly. A fix has been introduced in RN repo, but not yet present in any release: facebook/react-native@3132cc8. 2. When rejecting a promise the old code was accessing JS runtime in an unsafe way, this is fixed now. ### Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update (improves or adds clarity to existing documentation) ### Tested on - [x] iOS - [x] Android
1 parent 1d3224c commit 1a567c3

File tree

7 files changed

+70
-41
lines changed

7 files changed

+70
-41
lines changed

android/src/main/cpp/ETInstallerModule.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ void ETInstallerModule::injectJSIBindings() {
5353
jbyteArray byteData =
5454
(jbyteArray)env->CallStaticObjectMethod(cls, method, jUrl);
5555

56+
if (env->IsSameObject(byteData, NULL)) {
57+
throw std::runtime_error("Error fetching data from a url");
58+
}
59+
5660
int size = env->GetArrayLength(byteData);
5761
jbyte *bytes = env->GetByteArrayElements(byteData, JNI_FALSE);
5862
std::byte *dataBytePtr = reinterpret_cast<std::byte *>(bytes);

android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@ class ETInstaller(
2121
@JvmStatic
2222
@DoNotStrip
2323
@Throws(Exception::class)
24-
fun fetchByteDataFromUrl(source: String): ByteArray {
25-
val url = URL(source)
26-
val connection = url.openConnection()
27-
connection.connect()
24+
fun fetchByteDataFromUrl(source: String): ByteArray? {
25+
try {
26+
val url = URL(source)
27+
val connection = url.openConnection()
28+
connection.connect()
2829

29-
val inputStream: InputStream = connection.getInputStream()
30-
val data = inputStream.readBytes()
31-
inputStream.close()
30+
val inputStream: InputStream = connection.getInputStream()
31+
val data = inputStream.readBytes()
32+
inputStream.close()
3233

33-
return data
34+
return data
35+
} catch (exception: Throwable) {
36+
return null
37+
}
3438
}
3539
}
3640

common/rnexecutorch/data_processing/ImageProcessing.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,14 @@ cv::Mat readImage(const std::string &imageURI) {
9797
// local file
9898
auto url = ada::parse(imageURI);
9999
image = cv::imread(std::string{url->get_pathname()}, cv::IMREAD_COLOR);
100-
} else {
100+
} else if (imageURI.starts_with("http")) {
101101
// remote file
102102
std::vector<std::byte> imageData = fetchUrlFunc(imageURI);
103103
image = cv::imdecode(
104104
cv::Mat(1, imageData.size(), CV_8UC1, (void *)imageData.data()),
105105
cv::IMREAD_COLOR);
106+
} else {
107+
throw std::runtime_error("Read image error: unknown protocol");
106108
}
107109

108110
if (image.empty()) {

common/rnexecutorch/host_objects/ModelHostObject.h

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma once
2+
13
#include <cstdio>
24
#include <string>
35
#include <tuple>
@@ -20,38 +22,48 @@ template <typename Model> class ModelHostObject : public JsiHostObject {
2022
}
2123

2224
JSI_HOST_FUNCTION(forward) {
23-
2425
auto promise = promiseVendor.createPromise(
2526
[this, count, args, &runtime](std::shared_ptr<Promise> promise) {
26-
std::thread([this, promise = std::move(promise), count, args,
27-
&runtime]() {
28-
constexpr std::size_t forwardArgCount =
29-
jsiconversion::getArgumentCount(&Model::forward);
30-
if (forwardArgCount != count) {
31-
char errorMessage[100];
32-
std::snprintf(
33-
errorMessage, sizeof(errorMessage),
34-
"Argument count mismatch, was expecting: %zu but got: %zu",
35-
forwardArgCount, count);
27+
constexpr std::size_t forwardArgCount =
28+
jsiconversion::getArgumentCount(&Model::forward);
29+
if (forwardArgCount != count) {
30+
char errorMessage[100];
31+
std::snprintf(
32+
errorMessage, sizeof(errorMessage),
33+
"Argument count mismatch, was expecting: %zu but got: %zu",
34+
forwardArgCount, count);
3635

37-
promise->reject(errorMessage);
38-
return;
39-
}
36+
promise->reject(errorMessage);
37+
return;
38+
}
4039

40+
// Do the asynchronous work
41+
std::thread([this, promise = std::move(promise), args, &runtime]() {
4142
try {
4243
auto argsConverted = jsiconversion::createArgsTupleFromJsi(
4344
&Model::forward, args, runtime);
44-
promise->resolve([this, argsConverted = std::move(argsConverted)](
45-
jsi::Runtime &runtime) {
46-
auto result = std::apply(
47-
std::bind_front(&Model::forward, model), argsConverted);
48-
auto resultValue =
49-
jsiconversion::getJsiValue(std::move(result), runtime);
50-
return resultValue;
45+
auto result = std::apply(std::bind_front(&Model::forward, model),
46+
argsConverted);
47+
48+
promise->resolve([result =
49+
std::move(result)](jsi::Runtime &runtime) {
50+
return jsiconversion::getJsiValue(std::move(result), runtime);
5151
});
52+
} catch (const std::runtime_error &e) {
53+
// This catch should be merged with the next one
54+
// (std::runtime_error inherits from std::exception) HOWEVER react
55+
// native has broken RTTI which breaks proper exception type
56+
// checking. Remove when the following change is present in our
57+
// version:
58+
// https://github.com/facebook/react-native/commit/3132cc88dd46f95898a756456bebeeb6c248f20e
59+
promise->reject(e.what());
60+
return;
5261
} catch (const std::exception &e) {
5362
promise->reject(e.what());
5463
return;
64+
} catch (...) {
65+
promise->reject("Unknown error");
66+
return;
5567
}
5668
}).detach();
5769
});

common/rnexecutorch/jsi/JsiPromise.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ jsi::Value PromiseVendor::createPromise(
4040

4141
auto rejectWrapper = [reject, &runtime, callInvoker](
4242
const std::string &errorMessage) -> void {
43-
auto error = jsi::JSError(runtime, errorMessage);
44-
auto errorShared = std::make_shared<jsi::JSError>(error);
45-
callInvoker->invokeAsync([reject, &runtime, errorShared]() -> void {
43+
callInvoker->invokeAsync([reject, &runtime, errorMessage]() -> void {
44+
auto error = jsi::JSError(runtime, errorMessage);
45+
auto errorShared = std::make_shared<jsi::JSError>(error);
4646
reject->call(runtime, errorShared->value());
4747
});
4848
};

common/rnexecutorch/models/StyleTransfer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <span>
44

5+
#include <executorch/extension/tensor/tensor.h>
56
#include <opencv2/opencv.hpp>
67

78
#include <rnexecutorch/Log.h>

ios/RnExecutorch/ETInstaller.mm

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#import <React/RCTCallInvoker.h>
66
#import <ReactCommon/RCTTurboModule.h>
77
#include <rnexecutorch/RnExecutorchInstaller.h>
8+
#include <stdexcept>
89

910
using namespace facebook::react;
1011

@@ -26,14 +27,19 @@ @implementation ETInstaller
2627
assert(jsiRuntime != nullptr);
2728

2829
auto fetchUrl = [](std::string url) {
29-
NSString *nsUrlStr =
30-
[NSString stringWithCString:url.c_str()
31-
encoding:[NSString defaultCStringEncoding]];
32-
NSURL *nsUrl = [NSURL URLWithString:nsUrlStr];
33-
NSData *data = [NSData dataWithContentsOfURL:nsUrl];
34-
const std::byte *bytePtr = reinterpret_cast<const std::byte *>(data.bytes);
35-
int bufferLength = [data length];
36-
return std::vector<std::byte>(bytePtr, bytePtr + bufferLength);
30+
@try {
31+
NSString *nsUrlStr =
32+
[NSString stringWithCString:url.c_str()
33+
encoding:[NSString defaultCStringEncoding]];
34+
NSURL *nsUrl = [NSURL URLWithString:nsUrlStr];
35+
NSData *data = [NSData dataWithContentsOfURL:nsUrl];
36+
const std::byte *bytePtr =
37+
reinterpret_cast<const std::byte *>(data.bytes);
38+
int bufferLength = [data length];
39+
return std::vector<std::byte>(bytePtr, bytePtr + bufferLength);
40+
} @catch (NSException *exception) {
41+
throw std::runtime_error("Error fetching data from a url");
42+
}
3743
};
3844
rnexecutorch::RnExecutorchInstaller::injectJSIBindings(
3945
jsiRuntime, jsCallInvoker, fetchUrl);

0 commit comments

Comments
 (0)