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 @@ -386,10 +386,6 @@ JsValue CallbackHandlers::CallJavaMethod(JsRuntime &rt, const JsValue &caller, c
result = jEnv.CallCharMethodA(callerJavaObject, mid, javaArgs);
}

// The napi tree round-trips the jchar through a jstring and takes one
// byte of its UTF-8 form, which truncates anything outside ASCII.
// engine::String is UTF-8 only, so the transcode is explicit here and
// matches every other jchar path in this tree.
return ArgConverter::convertToJsString(rt, &result, 1);
}
case MethodReturnType::Short: {
Expand Down
45 changes: 0 additions & 45 deletions NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,51 +239,6 @@ ArgConverter::TypeLongOperationsCache *ArgConverter::GetTypeLongCache(JsRuntime
return cache;
}

JsValue ArgConverter::convertToJsString(JsRuntime &rt, const jchar *data, int length) {
if (data == nullptr || length <= 0) {
return convertToJsString(rt, std::string());
}

// Strict UTF-16 -> UTF-8, matching what napi_create_string_utf16 did inside
// the engine. Unpaired surrogates are emitted as U+FFFD rather than dropped,
// so a lone jchar (Type::Char, which is exactly one code unit) still yields a
// one-character JS string.
std::string utf8;
utf8.reserve((size_t) length);
for (int i = 0; i < length; i++) {
uint32_t cp = data[i];
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < length) {
uint32_t low = data[i + 1];
if (low >= 0xDC00 && low <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
i++;
} else {
cp = 0xFFFD;
}
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
cp = 0xFFFD;
}

if (cp < 0x80) {
utf8.push_back((char) cp);
} else if (cp < 0x800) {
utf8.push_back((char) (0xC0 | (cp >> 6)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
} else if (cp < 0x10000) {
utf8.push_back((char) (0xE0 | (cp >> 12)));
utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
} else {
utf8.push_back((char) (0xF0 | (cp >> 18)));
utf8.push_back((char) (0x80 | ((cp >> 12) & 0x3F)));
utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
}
}

return convertToJsString(rt, utf8);
}

u16string ArgConverter::ConvertToUtf16String(JsRuntime &rt, const JsValue &s) {
if (!s.isString()) {
return {};
Expand Down
22 changes: 14 additions & 8 deletions NativeScript/ffi/jni/jsi/conversion/ArgConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ namespace tns {
if (value == nullptr) return js_util::null();

JEnv jenv;
auto chars = jenv.GetStringUTFChars(value, JNI_FALSE);
auto length = jenv.GetStringUTFLength(value);
jsize length = jenv.GetStringLength(value);
const jchar *chars = jenv.GetStringChars(value, nullptr);
auto jsString = convertToJsString(rt, chars, length);
jenv.ReleaseStringUTFChars(value, chars);
jenv.ReleaseStringChars(value, chars);

return jsString;
}
Expand Down Expand Up @@ -65,14 +65,20 @@ namespace tns {
static std::u16string ConvertToUtf16String(JsRuntime &rt, const JsValue &s);

inline static jstring ConvertToJavaString(JsRuntime &rt, const JsValue &jsValue) {
char16_t stackBuffer[256];
size_t length = jsValue.copyUtf16(rt, stackBuffer, std::size(stackBuffer));
JEnv jenv;
return jenv.NewStringUTF(js_util::get_string_value(rt, jsValue).c_str());
if (length <= std::size(stackBuffer)) {
return jenv.NewString(reinterpret_cast<const jchar *>(stackBuffer), static_cast<jsize>(length));
}
std::u16string heapBuffer(length, u'\0');
jsValue.copyUtf16(rt, heapBuffer.data(), length);
return jenv.NewString(reinterpret_cast<const jchar *>(heapBuffer.data()), static_cast<jsize>(length));
}

// engine::String is UTF-8 only, so a UTF-16 payload (every jchar/jstring
// that reaches here) is transcoded on the way in rather than handed to a
// napi_create_string_utf16 equivalent.
static JsValue convertToJsString(JsRuntime &rt, const jchar *data, int length);
inline static JsValue convertToJsString(JsRuntime &rt, const jchar *data, int length) {
return JsValue::createStringFromUtf16(rt, reinterpret_cast<const char16_t *>(data), (size_t) length);
}

inline static JsValue convertToJsString(JsRuntime &rt, const std::string &s) {
return JsValue::createStringFromUtf8(rt, s.data(), s.size());
Expand Down
4 changes: 0 additions & 4 deletions NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ JsValue ArrayElementAccessor::GetArrayElement(JsRuntime &rt, const JsValue &arra
case 'C': {
jchar v;
jenv.GetCharArrayRegion((jcharArray) arr, startIndex, length, &v);
// The napi tree round-trips the jchar through a jstring and then takes
// one byte of its UTF-8 form, which truncates anything outside ASCII.
// engine::String is UTF-8 only, so the transcode is explicit here and
// is the same one every other jchar path in this tree uses.
return ArgConverter::convertToJsString(rt, &v, 1);
}
case 'S': {
Expand Down
4 changes: 0 additions & 4 deletions NativeScript/ffi/jni/jsi/metadata/FieldAccessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ FieldAccessor::GetJavaField(JsRuntime &rt, const JsValue &target, FieldCallbackD
result = jEnv.GetCharField(targetJavaObject, fieldId);
}

// The napi tree round-trips the jchar through a jstring and takes
// one byte of its UTF-8 form, which truncates anything outside
// ASCII. engine::String is UTF-8 only, so the transcode is
// explicit here and matches every other jchar path in this tree.
return ArgConverter::convertToJsString(rt, &result, 1);
}
case 'S': { // short
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,7 @@ napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, con
result = jEnv.CallCharMethodA(callerJavaObject, mid, javaArgs);
}

JniLocalRef str(jEnv.NewString(&result, 1));
jboolean bol = true;
const char *resP = jEnv.GetStringUTFChars(str, &bol);
returnValue = ArgConverter::convertToJsString(env, resP, 1);
jEnv.ReleaseStringUTFChars(str, resP);
returnValue = ArgConverter::convertToJsString(env, &result, 1);
break;
}
case MethodReturnType::Short: {
Expand Down
18 changes: 14 additions & 4 deletions NativeScript/ffi/jni/napi/conversion/ArgConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ namespace tns {
if (value == nullptr) return napi_util::null(env);

JEnv jenv;
auto chars = jenv.GetStringUTFChars(value,JNI_FALSE);
auto length = jenv.GetStringUTFLength(value);
jsize length = jenv.GetStringLength(value);
const jchar *chars = jenv.GetStringChars(value, nullptr);
auto jsString = convertToJsString(env, chars, length);
jenv.ReleaseStringUTFChars(value, chars);
jenv.ReleaseStringChars(value, chars);

return jsString;
}
Expand Down Expand Up @@ -64,8 +64,18 @@ namespace tns {
static std::u16string ConvertToUtf16String(napi_env env, napi_value s);

inline static jstring ConvertToJavaString(napi_env env, napi_value jsValue) {
size_t length = 0;
napi_get_value_string_utf16(env, jsValue, nullptr, 0, &length);
char16_t stackBuffer[256];
std::u16string heapBuffer;
char16_t *buffer = stackBuffer;
if (length >= std::size(stackBuffer)) {
heapBuffer.resize(length + 1);
buffer = heapBuffer.data();
}
napi_get_value_string_utf16(env, jsValue, buffer, length + 1, &length);
JEnv jenv;
return jenv.NewStringUTF(napi_util::get_string_value(env, jsValue, 0));
return jenv.NewString(reinterpret_cast<const jchar *>(buffer), static_cast<jsize>(length));
}

inline static napi_value convertToJsString(napi_env env, const jchar *data, int length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,7 @@ napi_value ArrayElementAccessor::GetArrayElement(napi_env env, napi_value array,
case 'C': {
jchar v;
jenv.GetCharArrayRegion((jcharArray) arr, startIndex, length, &v);
JniLocalRef s(jenv.NewString(&v, 1));
jboolean isCopy = false;
const char* singleChar = jenv.GetStringUTFChars(s, &isCopy);
NAPI_GUARD(napi_create_string_utf8(env, singleChar, 1, &value)) {}
jenv.ReleaseStringUTFChars(s, singleChar);
value = ArgConverter::convertToJsString(env, &v, 1);
break;
}
case 'S': {
Expand Down
6 changes: 1 addition & 5 deletions NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,7 @@ FieldAccessor::GetJavaField(napi_env env, napi_value target, FieldCallbackData *
result = jEnv.GetCharField(targetJavaObject, fieldId);
}

JniLocalRef str(jEnv.NewString(&result, 1));
jboolean bol = true;
const char *resP = jEnv.GetStringUTFChars(str, &bol);
fieldResult = ArgConverter::convertToJsString(env, resP, 1);
jEnv.ReleaseStringUTFChars(str, resP);
fieldResult = ArgConverter::convertToJsString(env, &result, 1);
break;
}
case 'S': { // short
Expand Down
29 changes: 29 additions & 0 deletions NativeScript/jsi/hermes/HermesRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

#include <jsi/jsi.h>

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <functional>
Expand Down Expand Up @@ -405,6 +406,8 @@ class Value {
// engine so the shared bridge can call one name.
std::string utf8(Runtime& runtime) const;
static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length);
static Value createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length);
size_t copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const;

// The jsi handle behind this value, materialising one for the inline scalar
// kinds. Returned by value because the scalar kinds have no handle to refer
Expand Down Expand Up @@ -913,6 +916,32 @@ inline Value Runtime::evaluateJavaScript(std::shared_ptr<StringBuffer> buffer,

// --- String ----------------------------------------------------------------

inline Value Value::createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length) {
return hermesengine::guard(runtime, [&] {
return Value::fromStorage(hermesengine::makeStorage(::facebook::jsi::Value(
::facebook::jsi::String::createFromUtf16(runtime.jsi(), data != nullptr ? data : u"", length))));
});
}

inline size_t Value::copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const {
String string = asString(runtime);
return hermesengine::guard(runtime, [&] {
size_t length = 0;
auto append = [&](bool ascii, const void* data, size_t count) {
size_t copied = length < capacity ? std::min(count, capacity - length) : 0;
if (ascii) {
const char* chars = static_cast<const char*>(data);
for (size_t i = 0; i < copied; i++) buffer[length + i] = static_cast<unsigned char>(chars[i]);
} else {
std::memcpy(buffer + length, data, copied * sizeof(char16_t));
}
length += count;
};
string.storage_->string(runtime.jsi()).getStringData(runtime.jsi(), append);
return length;
});
}

inline String String::createFromUtf8(Runtime& runtime, const char* value) {
const char* text = value != nullptr ? value : "";
return createFromUtf8(runtime, reinterpret_cast<const uint8_t*>(text),
Expand Down
2 changes: 2 additions & 0 deletions NativeScript/jsi/jsc/JSCRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ class Value {
// engine so the shared bridge can call one name.
std::string utf8(Runtime& runtime) const;
static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length);
static Value createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length);
size_t copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const;

JSValueRef local(Runtime& runtime) const {
switch (kind_) {
Expand Down
16 changes: 16 additions & 0 deletions NativeScript/jsi/jsc/JSCValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,22 @@ Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t len
reinterpret_cast<const uint8_t*>(data), length));
}

Value Value::createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length) {
JSStringRef string = JSStringCreateWithCharacters(reinterpret_cast<const JSChar*>(data), length);
Value result(runtime, String(runtime, string));
JSStringRelease(string);
return result;
}

size_t Value::copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const {
JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), nullptr);
if (string == nullptr) return 0;
size_t length = JSStringGetLength(string);
std::memcpy(buffer, JSStringGetCharactersPtr(string), std::min(length, capacity) * sizeof(JSChar));
JSStringRelease(string);
return length;
}

BigInt Value::getBigInt(Runtime& runtime) const { return BigInt(runtime, local(runtime)); }

Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const {
Expand Down
2 changes: 2 additions & 0 deletions NativeScript/jsi/quickjs/QuickJSRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ class Value {
// scope-rooted, so unlike V8 this still needs owning storage -- but it does
// not need a second reference.
static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length);
static Value createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length);
size_t copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const;

JSValue local(Runtime& runtime) const {
switch (kind_) {
Expand Down
23 changes: 23 additions & 0 deletions NativeScript/jsi/quickjs/QuickJSValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t len
JS_NewStringLen(runtime.context(), data != nullptr ? data : "", length);
return result;
}

Value Value::createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length) {
Value result;
result.kind_ = quickjsengine::ValueStorage::Kind::QuickJS;
result.storage_ =
std::make_shared<quickjsengine::ValueStorage>(quickjsengine::ValueStorage::Kind::QuickJS);
result.storage_->context = runtime.context();
result.storage_->value = JS_NewStringUTF16(
runtime.context(), reinterpret_cast<const uint16_t*>(data != nullptr ? data : u""), length);
return result;
}

size_t Value::copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const {
JSContext* context = runtime.context();
JSValue value = local(runtime);
size_t length = 0;
const uint16_t* units = JS_ToCStringLenUTF16(context, &length, value);
JS_FreeValue(context, value);
if (units == nullptr) return 0;
std::memcpy(buffer, units, std::min(length, capacity) * sizeof(char16_t));
JS_FreeCStringUTF16(context, units);
return length;
}
BigInt Value::getBigInt(Runtime& runtime) const {
JSValue value = local(runtime);
BigInt result(runtime, value);
Expand Down
2 changes: 2 additions & 0 deletions NativeScript/jsi/v8/V8Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,8 @@ class Value {
// Contract: the result must not outlive the current HandleScope. Callers that
// need an owning value keep using String::createFromUtf8.
static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length);
static Value createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length);
size_t copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const;

v8::Local<v8::Value> local(Runtime& runtime) const;

Expand Down
17 changes: 17 additions & 0 deletions NativeScript/jsi/v8/V8Value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,23 @@ Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t len
.ToLocalChecked());
}

Value Value::createStringFromUtf16(Runtime& runtime, const char16_t* data, size_t length) {
v8::Isolate* isolate = runtime.isolate();
return Value::borrowed(
isolate, v8::String::NewFromTwoByte(isolate,
reinterpret_cast<const uint16_t*>(data != nullptr ? data : u""),
v8::NewStringType::kNormal, static_cast<int>(length))
.ToLocalChecked());
}

size_t Value::copyUtf16(Runtime& runtime, char16_t* buffer, size_t capacity) const {
v8::Local<v8::String> string = local(runtime).As<v8::String>();
size_t length = static_cast<size_t>(string->Length());
string->WriteV2(runtime.isolate(), 0, static_cast<uint32_t>(std::min(length, capacity)),
reinterpret_cast<uint16_t*>(buffer), v8::String::WriteFlags::kNone);
return length;
}

BigInt Value::getBigInt(Runtime& runtime) const {
return BigInt(runtime, local(runtime).As<v8::BigInt>());
}
Expand Down
2 changes: 1 addition & 1 deletion vendor/jsc/jsc-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class JSString {
size_t length{JSStringGetLength(_string)};
const JSChar* chars{JSStringGetCharactersPtr(_string)};
size_t size{std::min(length, bufsize - 1)};
std::memcpy(buf, chars, size);
std::memcpy(buf, chars, size * sizeof(JSChar));
buf[size] = 0;
if (result != nullptr) {
*result = size;
Expand Down
Loading