Skip to content
Merged
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
1 change: 1 addition & 0 deletions test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ require("./tests/shared-array-buffer-test");
require("./tests/dex-interface-implementation");
require("./tests/testClassForNameDiscovery");
require("./tests/testInterfaceImplementation");
require("./tests/testPrimitiveCallbackReturns");
require("./tests/testRuntimeImplementedAPIs");
require("./tests/testsInstanceOfOperator");
require("./tests/testProxyReceivers");
Expand Down
108 changes: 108 additions & 0 deletions test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
describe("Primitive return values of JS callbacks", function () {
var current;
var impl = new com.tns.tests.PrimitiveReturnTest.Returns({
z: function () { return current(); },
b: function () { return current(); },
c: function () { return current(); },
s: function () { return current(); },
i: function () { return current(); },
j: function () { return current(); },
f: function () { return current(); },
d: function () { return current(); }
});

// Numbers at or beyond +-2^63 are left out: the boxed conversion casts them
// to int64, which is undefined and differs between arm64 and x86_64.
var values = {
"1": function () { return 1; },
"-1": function () { return -1; },
"0": function () { return 0; },
"-0": function () { return -0; },
"2^31": function () { return 2147483648; },
"-2^31-1": function () { return -2147483649; },
"2^40": function () { return 1099511627776; },
"2^60": function () { return 1152921504606846976; },
"1.5": function () { return 1.5; },
"-1.5": function () { return -1.5; },
"1e-40": function () { return 1e-40; },
"3.5e38": function () { return 3.5e38; },
"NaN": function () { return NaN; },
"Infinity": function () { return Infinity; },
"-Infinity": function () { return -Infinity; },
"-2.5": function () { return -2.5; },
"true": function () { return true; },
"false": function () { return false; },
"'1'": function () { return "1"; },
"'a'": function () { return "a"; },
"null": function () { return null; },
"undefined": function () { return undefined; },
"Number(5)": function () { return new Number(5); },
"Number(2.5)": function () { return new Number(2.5); },
"Boolean(true)": function () { return new Boolean(true); },
"Boolean(false)": function () { return new Boolean(false); },
"long(5)": function () { return long(5); },
"byte(2)": function () { return byte(2); },
"short(3)": function () { return short(3); },
"char('a')": function () { return char("a"); },
"float(1.5)": function () { return float(1.5); },
"double(2)": function () { return double(2); },
"{}": function () { return {}; }
};

// Captured from the boxed conversion; columns are z b c s i j f d.
var expected = {
"0": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:0 ok:0 ex:ClassCastException ex:ClassCastException",
"1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1 ok:1 ex:ClassCastException ex:ClassCastException",
"-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1 ok:-1 ex:ClassCastException ex:ClassCastException",
"-0": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:0 ok:0 ex:ClassCastException ex:ClassCastException",
"2^31": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2147483648 ex:ClassCastException ex:ClassCastException",
"-2^31-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2147483649 ex:ClassCastException ex:ClassCastException",
"2^40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1099511627776 ex:ClassCastException ex:ClassCastException",
"2^60": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1152921504606846976 ex:ClassCastException ex:ClassCastException",
"1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.5 ok:1.5",
"-1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1.5",
"1e-40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.0E-40",
"3.5e38": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3.5E38",
"NaN": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:NaN",
"Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:Infinity",
"-Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-Infinity",
"-2.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2.5",
"true": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"false": "ok:false ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"'1'": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"'a'": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"null": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0",
"undefined": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0",
"Number(5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:5 ok:5 ex:ClassCastException ex:ClassCastException",
"Number(2.5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2.5 ok:2.5",
"Boolean(true)": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"Boolean(false)": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"long(5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:5 ex:ClassCastException ex:ClassCastException",
"byte(2)": "ex:ClassCastException ok:2 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"short(3)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"char('a')": "ex:ClassCastException ex:ClassCastException ok:97 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException",
"float(1.5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.5 ex:ClassCastException",
"double(2)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2.0",
"{}": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0"
};

var kinds = ["z", "b", "c", "s", "i", "j", "f", "d"];

function matrix() {
var result = {};
Object.keys(values).forEach(function (name) {
current = values[name];
result[name] = kinds.map(function (kind) {
return com.tns.tests.PrimitiveReturnTest.call(impl, kind);
}).join(" ");
});
return result;
}

it("converts every JS value the same way for each primitive return type", function () {
var actual = matrix();
Object.keys(values).forEach(function (name) {
expect(name + " | " + actual[name]).toBe(name + " | " + expected[name]);
});
});
});
53 changes: 53 additions & 0 deletions test-app/app/src/main/java/com/tns/tests/PrimitiveReturnTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.tns.tests;

/**
* Calls each primitive-returning method of a JS implementation and reports either the
* returned value or the class of what was thrown, so a spec can pin how JS return values
* convert to each primitive type.
*/
public class PrimitiveReturnTest {
public interface Returns {
boolean z();

byte b();

char c();

short s();

int i();

long j();

float f();

double d();
}

public static String call(Returns r, String kind) {
try {
switch (kind) {
case "z":
return "ok:" + r.z();
case "b":
return "ok:" + r.b();
case "c":
return "ok:" + (int) r.c();
case "s":
return "ok:" + r.s();
case "i":
return "ok:" + r.i();
case "j":
return "ok:" + r.j();
case "f":
return "ok:" + r.f();
case "d":
return "ok:" + r.d();
default:
throw new IllegalArgumentException(kind);
}
} catch (Throwable t) {
return "ex:" + t.getClass().getSimpleName();
}
}
}
92 changes: 91 additions & 1 deletion test-app/runtime/src/main/cpp/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
#include "include/libplatform/libplatform.h"
#include "sys/system_properties.h"

#include <cfloat>
#include <climits>
#include <cmath>
#include <cstring>

#ifdef APPLICATION_IN_DEBUG
// #include "NetworkDomainCallbackHandlers.h"
#include "JsV8InspectorClient.h"
Expand Down Expand Up @@ -497,10 +502,86 @@ jobject Runtime::RunScript(JNIEnv* _env, jobject obj, jstring scriptFile) {
return res;
}

/*
* The primitive-returning cases of JsArgToArrayConverter::ConvertArg that box
* into exactly the declared wrapper type, which is all the generated binding's
* cast accepts. Everything else keeps the boxed conversion, including the
* values it turns into a ClassCastException or a defaulted null.
*/
static bool TryGetPrimitiveReturnBits(Isolate* isolate,
const Local<Value>& value, Type type,
jlong& bits) {
if (value.IsEmpty()) {
return false;
}

if (type == Type::Boolean) {
if (!value->IsBoolean() && !value->IsBooleanObject()) {
return false;
}
// ToBoolean, as the boxed path does: a Boolean object is always true.
bits = value->BooleanValue(isolate) ? 1 : 0;
return true;
}

// Number objects are left to the boxed path: reading one calls valueOf,
// which must not run twice if this path then declines.
if (!value->IsNumber()) {
return false;
}

if (type == Type::Int && value->IsInt32()) {
bits = value.As<Int32>()->Value();
return true;
}

double d = value.As<Number>()->Value();
// Beyond int64 the boxed path's double -> int64 cast is undefined.
constexpr double kTwoPow63 = 9223372036854775808.0;
if (std::isfinite(d) && std::fabs(d) >= kTwoPow63) {
return false;
}
bool isWholeNumber =
std::isfinite(d) && d == static_cast<double>(static_cast<int64_t>(d));

switch (type) {
case Type::Int:
if (isWholeNumber && INT_MIN <= d && d <= INT_MAX) {
bits = static_cast<jint>(d);
return true;
}
return false;
case Type::Long:
if (isWholeNumber) {
bits = static_cast<jlong>(d);
return true;
}
return false;
case Type::Float:
if (!isWholeNumber && FLT_MIN <= d && d <= FLT_MAX) {
float f = static_cast<jfloat>(d);
int32_t raw;
memcpy(&raw, &f, sizeof(raw));
bits = raw;
return true;
}
return false;
case Type::Double:
if (!isWholeNumber) {
memcpy(&bits, &d, sizeof(bits));
return true;
}
return false;
default:
return false;
}
}

jobject Runtime::CallJSMethodNative(JNIEnv* _env, jobject obj,
jint javaObjectID, jstring methodName,
jint retType, jboolean isConstructor,
const JavaCallArgs& args) {
const JavaCallArgs& args,
jlongArray primitiveResult) {
SET_PROFILER_FRAME();

auto isolate = m_isolate;
Expand Down Expand Up @@ -540,6 +621,15 @@ jobject Runtime::CallJSMethodNative(JNIEnv* _env, jobject obj,
return nullptr;
}

jlong bits;
if (primitiveResult != nullptr &&
TryGetPrimitiveReturnBits(m_isolate, jsResult, static_cast<Type>(retType),
bits)) {
jlong out[2] = {bits, 1};
_env->SetLongArrayRegion(primitiveResult, 0, 2, out);
return nullptr;
}

int classReturnType = retType;
jobject javaObject =
ConvertJsValueToJavaObject(env, jsResult, classReturnType);
Expand Down
2 changes: 1 addition & 1 deletion test-app/runtime/src/main/cpp/Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class Runtime {
void RunModule(const char *moduleName);
void RunWorker(const std::string& filePath);
jobject RunScript(JNIEnv* _env, jobject obj, jstring scriptFile);
jobject CallJSMethodNative(JNIEnv* _env, jobject obj, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, const JavaCallArgs& args);
jobject CallJSMethodNative(JNIEnv* _env, jobject obj, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, const JavaCallArgs& args, jlongArray primitiveResult);
void CreateJSInstanceNative(JNIEnv* _env, jobject obj, jobject javaObject, jint javaObjectID, jstring className);
jint GenerateNewObjectId(JNIEnv* env, jobject obj);
void AdjustAmountOfExternalAllocatedMemory();
Expand Down
4 changes: 2 additions & 2 deletions test-app/runtime/src/main/cpp/com_tns_Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ extern "C" JNIEXPORT jobject Java_com_tns_Runtime_runScript(JNIEnv* _env, jobjec
return result;
}

extern "C" JNIEXPORT jobject Java_com_tns_Runtime_callJSMethodNative(JNIEnv* _env, jobject obj, jint runtimeId, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, jintArray argTypes, jlongArray argValues, jobjectArray argRefs) {
extern "C" JNIEXPORT jobject Java_com_tns_Runtime_callJSMethodNative(JNIEnv* _env, jobject obj, jint runtimeId, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, jintArray argTypes, jlongArray argValues, jobjectArray argRefs, jlongArray primitiveResult) {
JEnv::EntryScope envScope(_env);
jobject result = nullptr;

Expand All @@ -211,7 +211,7 @@ extern "C" JNIEXPORT jobject Java_com_tns_Runtime_callJSMethodNative(JNIEnv* _en
v8::Context::Scope context_scope(context);

try {
result = runtime->CallJSMethodNative(_env, obj, javaObjectID, methodName, retType, isConstructor, JavaCallArgs{argTypes, argValues, argRefs});
result = runtime->CallJSMethodNative(_env, obj, javaObjectID, methodName, retType, isConstructor, JavaCallArgs{argTypes, argValues, argRefs}, primitiveResult);
} catch (NativeScriptException& e) {
e.ReThrowToJava();
} catch (std::exception e) {
Expand Down
Loading
Loading