Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Context.throwException on Java API #1272

Merged
merged 1 commit into from
Nov 3, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ public Optional<JavaScriptValue> callback(Context context, JavaScriptValue recei

assertFalse(context.exceptionWasThrown());
Evaluator.evalScript(context, "new asdf()", "test.js", false);
assertFalse(context.lastThrownException().isPresent());
assertTrue(context.lastThrownException().isPresent());

ret = Evaluator.evalScript(context, "asdf(1, 2, 3, 4)", "test.js", false);
assertEquals(ret.get().asInt32(), 4);
Expand Down Expand Up @@ -1799,6 +1799,67 @@ public void errorObjectTest()
assertTrue(e.toString(context).get().toJavaString().equals(kinds[i].name() + ": test" + i));
}

// failed to throw exception
assertFalse(context.throwException(JavaScriptValue.create(123)));
assertFalse(context.exceptionWasThrown());

Bridge.register(context, "Native", "throwsException", new Bridge.Adapter() {
@Override
public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptValue> data) {
context.throwException(JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf"));
assertTrue(false);
return null;
}
});

// will fail
Optional<JavaScriptValue> ret = Evaluator.evalScript(context, "Native.throwsException()", "");
assertFalse(ret.isPresent());
assertTrue(context.exceptionWasThrown());
assertTrue(context.lastThrownException().get().isErrorObject());

JavaScriptFunctionObject fn =
context.getGlobalObject().get(context, JavaScriptString.create("Native"))
.get().asScriptObject().get(context, JavaScriptString.create("throwsException")).get().asScriptFunctionObject();
// will fail
ret = fn.call(context, context.getGlobalObject(), new JavaScriptValue[]{});
assertFalse(ret.isPresent());
assertTrue(context.exceptionWasThrown());
assertTrue(context.lastThrownException().get().isErrorObject());

Bridge.register(context, "Native", "throwsException", new Bridge.Adapter() {
@Override
public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptValue> data) {
JavaScriptErrorObject err = JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf");
err.setExtraData(Optional.of(new RuntimeException("asdf")));
context.throwException(err);
assertTrue(false);
return null;
}
});
Evaluator.evalScript(context, "Native.throwsException()", "");
ret = Evaluator.evalScript(context, "Native.throwsException()", "");
assertFalse(ret.isPresent());
assertTrue(context.exceptionWasThrown());
JavaScriptErrorObject err = context.lastThrownException().get().asScriptErrorObject();
assertTrue(err.extraData().get() instanceof RuntimeException);

ArrayList<Object> thenCalled = new ArrayList<>();
JavaScriptPromiseObject promise = JavaScriptPromiseObject.create(context);
promise.then(context, JavaScriptValue.createUndefined(), JavaScriptJavaCallbackFunctionObject.create(context, "", 0, false, new JavaScriptJavaCallbackFunctionObject.Callback() {
@Override
public Optional<JavaScriptValue> callback(Context context, JavaScriptValue receiverValue, JavaScriptValue[] arguments) {
thenCalled.add(new Object());
assertTrue(arguments[0].isErrorObject());
assertTrue(arguments[0].toString(context).get().toJavaString().equals("Error: asdf"));
return Optional.empty();
}
}));
promise.reject(context, JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf"));
assertTrue(thenCalled.size() == 0);
vmInstance.executeEveryPendingJobIfExists();
assertTrue(thenCalled.size() == 1);

context = null;
vmInstance = null;
finalizeEngine();
Expand Down
53 changes: 47 additions & 6 deletions build/android/escargot/src/main/cpp/EscargotJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,59 @@ void throwJavaRuntimeException(ExecutionStateRef* state)
state->throwException(ErrorObjectRef::create(state, ErrorObjectRef::None, StringRef::createFromASCII("Java runtime exception")));
}

jobject storeExceptionOnContextAndReturnsIt(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
bool hasJavaScriptRuntimeExceptionOnEnv(JNIEnv* env)
{
if (env->ExceptionCheck()) {
jthrowable exception = env->ExceptionOccurred();
env->ExceptionClear();
bool ret = env->IsInstanceOf(exception, env->FindClass("com/samsung/lwe/escargot/internal/JavaScriptRuntimeException"));
env->Throw(exception);
return ret;
}
return false;
}

OptionalRef<ValueRef> extractExceptionFromEnv(JNIEnv* env)
{
if (env->ExceptionCheck()) {
jthrowable exception = env->ExceptionOccurred();
env->ExceptionClear();
bool ret = env->IsInstanceOf(exception, env->FindClass("com/samsung/lwe/escargot/internal/JavaScriptRuntimeException"));
if (ret) {
jclass clz = env->FindClass("com/samsung/lwe/escargot/internal/JavaScriptRuntimeException");
jobject jv = env->CallObjectMethod(exception, env->GetMethodID(clz, "exception", "()Lcom/samsung/lwe/escargot/JavaScriptValue;"));
ValueRef* v = unwrapValueRefFromValue(env, env->GetObjectClass(jv), jv);
return v;
} else {
env->Throw(exception);
}
}

return nullptr;
}

bool hasJavaExcpetion(JNIEnv* env)
{
if (env->ExceptionCheck() && !hasJavaScriptRuntimeExceptionOnEnv(env)) {
return true;
}
return false;
}

jobject storeExceptionOnContextAndReturnsIt(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
{
if (hasJavaExcpetion(env)) {
return nullptr;
}
auto exceptionOnEnv = extractExceptionFromEnv(env);
jclass optionalClazz = env->FindClass("java/util/Optional");
// store exception to context
auto fieldId = env->GetFieldID(env->GetObjectClass(contextObject), "m_lastThrownException", "Ljava/util/Optional;");
ValueRef* exception = exceptionOnEnv ? exceptionOnEnv.get() : evaluatorResult.error.value();
auto fieldValue = env->CallStaticObjectMethod(optionalClazz,
env->GetStaticMethodID(optionalClazz, "of",
"(Ljava/lang/Object;)Ljava/util/Optional;"),
createJavaObjectFromValue(env, evaluatorResult.error.value()));
createJavaObjectFromValue(env, exception));
env->SetObjectField(contextObject, fieldId, fieldValue);

return env->CallStaticObjectMethod(optionalClazz, env->GetStaticMethodID(optionalClazz, "empty",
Expand All @@ -210,7 +251,7 @@ jobject storeExceptionOnContextAndReturnsIt(JNIEnv* env, jobject contextObject,

jobject createOptionalValueFromEvaluatorJavaScriptValueResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
{
if (env->ExceptionCheck()) {
if (hasJavaExcpetion(env)) {
return nullptr;
}
if (evaluatorResult.isSuccessful()) {
Expand All @@ -226,7 +267,7 @@ jobject createOptionalValueFromEvaluatorJavaScriptValueResult(JNIEnv* env, jobje

jobject createOptionalValueFromEvaluatorBooleanResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
{
if (env->ExceptionCheck()) {
if (hasJavaExcpetion(env)) {
return nullptr;
}
if (evaluatorResult.isSuccessful()) {
Expand All @@ -245,7 +286,7 @@ jobject createOptionalValueFromEvaluatorBooleanResult(JNIEnv* env, jobject conte

jobject createOptionalValueFromEvaluatorIntegerResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
{
if (env->ExceptionCheck()) {
if (hasJavaExcpetion(env)) {
return nullptr;
}
if (evaluatorResult.isSuccessful()) {
Expand All @@ -264,7 +305,7 @@ jobject createOptionalValueFromEvaluatorIntegerResult(JNIEnv* env, jobject conte

jobject createOptionalValueFromEvaluatorDoubleResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult)
{
if (env->ExceptionCheck()) {
if (hasJavaExcpetion(env)) {
return nullptr;
}
if (evaluatorResult.isSuccessful()) {
Expand Down
1 change: 1 addition & 0 deletions build/android/escargot/src/main/cpp/EscargotJNI.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ StringRef* createJSStringFromJava(JNIEnv* env, jstring str);
std::string createStringFromJava(JNIEnv* env, jstring str);
jstring createJavaStringFromJS(JNIEnv* env, StringRef* string);
void throwJavaRuntimeException(ExecutionStateRef* state);
OptionalRef<ValueRef> extractExceptionFromEnv(JNIEnv* env);
jobject storeExceptionOnContextAndReturnsIt(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult);
jobject createOptionalValueFromEvaluatorJavaScriptValueResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult);
jobject createOptionalValueFromEvaluatorBooleanResult(JNIEnv* env, jobject contextObject, ContextRef* context, Evaluator::EvaluatorResult& evaluatorResult);
Expand Down
14 changes: 14 additions & 0 deletions build/android/escargot/src/main/cpp/JNIContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,17 @@ Java_com_samsung_lwe_escargot_Context_getGlobalObject(JNIEnv* env, jobject thiz)
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(thiz), thiz);
return createJavaObjectFromValue(env, contextRef->get()->globalObject());
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_samsung_lwe_escargot_Context_throwException(JNIEnv* env, jobject thiz, jobject exception)
{
THROW_NPE_RETURN_NULL(exception, "JavaScriptValue");
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(thiz), thiz);
if (contextRef->get()->canThrowException()) {
jclass clz = env->FindClass("com/samsung/lwe/escargot/internal/JavaScriptRuntimeException");
jobject obj = env->NewObject(clz, env->GetMethodID(clz, "<init>", "(Lcom/samsung/lwe/escargot/JavaScriptValue;)V"), exception);
env->Throw(static_cast<jthrowable>(obj));
}
return false;
}
13 changes: 1 addition & 12 deletions build/android/escargot/src/main/cpp/JNIEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,5 @@ Java_com_samsung_lwe_escargot_Evaluator_evalScript(JNIEnv* env, jclass clazz, jo
Evaluator::EvaluatorResult result = evalScript(ptr->get(), createJSStringFromJava(env, source),
createJSStringFromJava(env, sourceFileName), shouldPrintScriptResult,
shouldExecutePendingJobsAtEnd, false);
if (env->ExceptionCheck()) {
return nullptr;
}
jclass optionalClazz = env->FindClass("java/util/Optional");
if (result.isSuccessful()) {
return env->CallStaticObjectMethod(optionalClazz,
env->GetStaticMethodID(optionalClazz, "of",
"(Ljava/lang/Object;)Ljava/util/Optional;"),
createJavaObjectFromValue(env, result.result));
}
return env->CallStaticObjectMethod(optionalClazz, env->GetStaticMethodID(optionalClazz, "empty",
"()Ljava/util/Optional;"));
return createOptionalValueFromEvaluatorJavaScriptValueResult(env, context, ptr->get(), result);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public Optional<JavaScriptValue> lastThrownException()
return lastThrownException;
}
public native JavaScriptGlobalObject getGlobalObject();
public native boolean throwException(JavaScriptValue exception);

protected Optional<JavaScriptValue> m_lastThrownException = Optional.empty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ protected JavaScriptErrorObject(long nativePointer)
super(nativePointer);
}

enum ErrorKind {
public enum ErrorKind {
None,
ReferenceError,
TypeError,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.samsung.lwe.escargot.internal;

import com.samsung.lwe.escargot.JavaScriptValue;

public class JavaScriptRuntimeException extends RuntimeException {
public JavaScriptRuntimeException(JavaScriptValue exception)
{
this.m_exception = exception;
}

public JavaScriptValue exception() {
return m_exception;
}

private JavaScriptValue m_exception;
}
5 changes: 5 additions & 0 deletions src/api/EscargotPublic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2917,6 +2917,11 @@ VMInstanceRef* ContextRef::vmInstance()
return toRef(ctx->vmInstance());
}

bool ContextRef::canThrowException()
{
return toImpl(this)->canThrowException();
}

void ContextRef::throwException(ValueRef* exceptionValue)
{
ExecutionState s(toImpl(this));
Expand Down
3 changes: 2 additions & 1 deletion src/api/EscargotPublic.h
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,8 @@ class ESCARGOT_EXPORT ContextRef {
// this setter try to update `globalThis` value on GlobalObject
void setGlobalObjectProxy(ObjectRef* newGlobalObjectProxy);

void throwException(ValueRef* exceptionValue); // if you use this function without Evaluator, your program will crash :(
bool canThrowException();
void throwException(ValueRef* exceptionValue);

bool initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient);
// available options(separator is ';')
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ Context::Context(VMInstance* instance)
#endif
}

bool Context::canThrowException()
{
return vmInstance()->currentSandBox();
}

void Context::throwException(ExecutionState& state, const Value& exception)
{
if (LIKELY(vmInstance()->currentSandBox() != nullptr)) {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class Context : public gc {
return m_toStringRecursionPreventer;
}

bool canThrowException();
void throwException(ExecutionState& state, const Value& exception);

// this is not compatible with ECMAScript
Expand Down