Skip to content

Commit

Permalink
Compute stacktrace when creating ErrorObject
Browse files Browse the repository at this point in the history
Signed-off-by: Seonghyun Kim <[email protected]>
  • Loading branch information
ksh8281 authored and clover2123 committed Nov 9, 2023
1 parent 4581040 commit 4f8582e
Show file tree
Hide file tree
Showing 23 changed files with 454 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1827,22 +1827,85 @@ public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptVa
assertTrue(context.exceptionWasThrown());
assertTrue(context.lastThrownException().get().isErrorObject());

Bridge.register(context, "Native", "throwsException", new Bridge.Adapter() {
Bridge.register(context, "Native", "empty", new Bridge.Adapter() {
@Override
public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptValue> data) {
JavaScriptErrorObject err = JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf");
Optional<JavaScriptString> stack = err.stack(context);
return Optional.of(stack.get());
}
});

ret = Evaluator.evalScript(context, "function foo() { return Native.empty() }\nfunction bar() { return foo() }\nbar();", "test.js");
assertTrue(ret.isPresent());
assertFalse(context.exceptionWasThrown());
assertEquals(ret.get().asScriptString().toJavaString(), "at function empty() { [native function] } \n" +
"at test.js:1:24\n" +
"function foo() { return Native.empty() }\n" +
" ^\n" +
"at test.js:2:24\n" +
"function bar() { return foo() }\n" +
" ^\n" +
"at test.js:3:1\n" +
"bar()\n" +
"^");

final JavaScriptErrorObject err = JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf");
assertEquals(err.stack(context).get().toJavaString(), "");
Bridge.register(context, "Native", "throwsException", new Bridge.Adapter() {
@Override
public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptValue> data) {
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()", "");
ret = Evaluator.evalScript(context, "function foo() { Native.throwsException() }\nfunction bar() { foo() }\nbar();", "test.js");
assertFalse(ret.isPresent());
assertTrue(context.exceptionWasThrown());
JavaScriptErrorObject err = context.lastThrownException().get().asScriptErrorObject();
assertTrue(err.extraData().get() instanceof RuntimeException);
JavaScriptErrorObject errResult = context.lastThrownException().get().asScriptErrorObject();
assertTrue(errResult.extraData().get() instanceof RuntimeException);
assertEquals(errResult.stack(context).get().toJavaString(),
"at function throwsException() { [native function] } \n" +
"at test.js:1:17\n" +
"function foo() { Native.throwsException() }\n" +
" ^\n" +
"at test.js:2:17\n" +
"function bar() { foo() }\n" +
" ^\n" +
"at test.js:3:1\n" +
"bar()\n" +
"^");

JavaScriptJavaCallbackFunctionObject callbackFunctionObject =
JavaScriptJavaCallbackFunctionObject.create(context,
"fnname",
0,
false,
new JavaScriptJavaCallbackFunctionObject.Callback() {
@Override
public Optional<JavaScriptValue> callback(Context context, JavaScriptValue receiverValue, JavaScriptValue[] arguments) {
JavaScriptErrorObject err = JavaScriptErrorObject.create(context, JavaScriptErrorObject.ErrorKind.None, "asdf");
Optional<JavaScriptString> stack = err.stack(context);
return Optional.of(stack.get());
}
});
context.getGlobalObject().set(context, JavaScriptString.create("asdf"), callbackFunctionObject);
ret = Evaluator.evalScript(context, "function foo() { return asdf() }\nfunction bar() { return foo() }\nbar();", "test.js");
assertTrue(ret.isPresent());
assertFalse(context.exceptionWasThrown());

assertEquals(ret.get().asScriptString().toJavaString(), "at function fnname() { [native function] } \n" +
"at test.js:1:24\n" +
"function foo() { return asdf() }\n" +
" ^\n" +
"at test.js:2:24\n" +
"function bar() { return foo() }\n" +
" ^\n" +
"at test.js:3:1\n" +
"bar()\n" +
"^");

ArrayList<Object> thenCalled = new ArrayList<>();
JavaScriptPromiseObject promise = JavaScriptPromiseObject.create(context);
Expand Down
1 change: 1 addition & 0 deletions build/android/escargot/src/main/cpp/EscargotJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

JavaVM* g_jvm;
size_t g_nonPointerValueLast = reinterpret_cast<size_t>(ValueRef::createUndefined());
thread_local std::vector<ExecutionStateRef*> ExecutionStateRefTracker::g_lastExecutionStateVector;

jobject createJavaValueObject(JNIEnv* env, jclass clazz, ValueRef* value)
{
Expand Down
38 changes: 38 additions & 0 deletions build/android/escargot/src/main/cpp/EscargotJNI.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include <jni.h>
#include <EscargotPublic.h>
#include <vector>
#include <set>
#include <cassert>

using namespace Escargot;
Expand Down Expand Up @@ -144,4 +146,40 @@ PersistentRefHolder<NativeType>* getPersistentPointerFromJava(JNIEnv *env, jclas
return pVMRef;
}

class ExecutionStateRefTracker {
public:
ExecutionStateRefTracker(ExecutionStateRef* newValue)
{
g_lastExecutionStateVector.push_back(newValue);
}

~ExecutionStateRefTracker()
{
g_lastExecutionStateVector.pop_back();
}
private:
friend class ScriptEvaluator;
static thread_local std::vector<ExecutionStateRef*> g_lastExecutionStateVector;
};

class ScriptEvaluator {
public:
template <typename... Args, typename F>
static Evaluator::EvaluatorResult execute(ContextRef* ctx, F&& closure, Args... args)
{
typedef ValueRef* (*Closure)(ExecutionStateRef * state, Args...);
if (ExecutionStateRefTracker::g_lastExecutionStateVector.size()) {
return Evaluator::execute(ExecutionStateRefTracker::g_lastExecutionStateVector.back(), [](ExecutionStateRef * state, Closure closure, Args... args) -> ValueRef* {
ExecutionStateRefTracker tracker(state);
return closure(state, args...);
}, Closure(closure), args...);
} else {
return Evaluator::execute(ctx, [](ExecutionStateRef * state, Closure closure, Args... args) -> ValueRef* {
ExecutionStateRefTracker tracker(state);
return closure(state, args...);
}, Closure(closure), args...);
}
}
};

#endif //ESCARGOT_ANDROID_ESCARGOTJNI_H
6 changes: 3 additions & 3 deletions build/android/escargot/src/main/cpp/JNIArrayObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Java_com_samsung_lwe_escargot_JavaScriptArrayObject_create(JNIEnv* env, jclass c
THROW_NPE_RETURN_NULL(context, "Context");

auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state) -> ValueRef* {
return ArrayObjectRef::create(state);
});

Expand All @@ -45,7 +45,7 @@ Java_com_samsung_lwe_escargot_JavaScriptArrayObject_length(JNIEnv* env, jobject
ArrayObjectRef* thisValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(thiz), thiz)->asArrayObject();

int64_t length = 0;
auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ArrayObjectRef* thisValueRef, int64_t* pLength) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ArrayObjectRef* thisValueRef, int64_t* pLength) -> ValueRef* {
*pLength = static_cast<int64_t>(thisValueRef->length(state));
return ValueRef::createUndefined();
}, thisValueRef, &length);
Expand All @@ -63,7 +63,7 @@ Java_com_samsung_lwe_escargot_JavaScriptArrayObject_setLength(JNIEnv* env, jobje
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
ArrayObjectRef* thisValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(thiz), thiz)->asArrayObject();

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ArrayObjectRef* thisValueRef, jlong pLength) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ArrayObjectRef* thisValueRef, jlong pLength) -> ValueRef* {
if (pLength >= 0) {
thisValueRef->setLength(state, static_cast<uint64_t>(pLength));
} else {
Expand Down
5 changes: 3 additions & 2 deletions build/android/escargot/src/main/cpp/JNIBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Java_com_samsung_lwe_escargot_Bridge_register(JNIEnv* env, jclass clazz, jobject

adapter = env->NewGlobalRef(adapter);

auto evalResult = Evaluator::execute(contextPtr->get(),
auto evalResult = ScriptEvaluator::execute(contextPtr->get(),
[](ExecutionStateRef* state, JNIEnv* env, jobject adapter,
StringRef* jsObjectName,
StringRef* jsPropertyName) -> ValueRef* {
Expand All @@ -59,11 +59,12 @@ Java_com_samsung_lwe_escargot_Bridge_register(JNIEnv* env, jclass clazz, jobject
}

FunctionObjectRef::NativeFunctionInfo info(
AtomicStringRef::emptyAtomicString(),
AtomicStringRef::create(state->context(), jsPropertyName),
[](ExecutionStateRef* state,
ValueRef* thisValue, size_t argc,
ValueRef** argv,
bool isConstructorCall) -> ValueRef* {
ExecutionStateRefTracker tracker(state);
FunctionObjectRef* callee = state->resolveCallee().get();

jobject jo = ensureScriptObjectExtraData(
Expand Down
7 changes: 7 additions & 0 deletions build/android/escargot/src/main/cpp/JNIContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ Java_com_samsung_lwe_escargot_Context_throwException(JNIEnv* env, jobject thiz,
THROW_NPE_RETURN_NULL(exception, "JavaScriptValue");
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(thiz), thiz);
if (contextRef->get()->canThrowException()) {
ValueRef* exceptionRef = unwrapValueRefFromValue(env, env->GetObjectClass(exception), exception);
if (exceptionRef->isErrorObject()) {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ValueRef* exceptionRef) -> ValueRef* {
exceptionRef->asErrorObject()->updateStackTraceData(state);
return ValueRef::createUndefined();
}, exceptionRef);
}
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));
Expand Down
19 changes: 18 additions & 1 deletion build/android/escargot/src/main/cpp/JNIErrorObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,28 @@ Java_com_samsung_lwe_escargot_JavaScriptErrorObject_create(JNIEnv* env, jclass c
StringRef* jsMessage = createJSStringFromJava(env, message);

auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state,
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state,
ErrorObjectRef::Code code, StringRef* jsMessage) -> ValueRef* {
return ErrorObjectRef::create(state, code, jsMessage);
}, code, jsMessage);

assert(evaluatorResult.isSuccessful());
return createJavaObjectFromValue(env, evaluatorResult.result->asErrorObject());
}

extern "C"
JNIEXPORT jobject JNICALL
Java_com_samsung_lwe_escargot_JavaScriptErrorObject_stack(JNIEnv* env, jobject thiz,
jobject context)
{
THROW_NPE_RETURN_NULL(context, "Context");

auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
ErrorObjectRef* thisValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(thiz), thiz)->asErrorObject();

auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ErrorObjectRef* thisValueRef) -> ValueRef* {
return thisValueRef->getOwnProperty(state, StringRef::createFromASCII("stack"))->toString(state);
}, thisValueRef);

return createOptionalValueFromEvaluatorJavaScriptValueResult(env, context, contextRef->get(), evaluatorResult);
}
19 changes: 10 additions & 9 deletions build/android/escargot/src/main/cpp/JNIEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,22 @@ static Evaluator::EvaluatorResult evalScript(ContextRef* context, StringRef* sou
return evalResult;
}

auto evalResult = Evaluator::execute(context, [](ExecutionStateRef* state, ScriptRef* script) -> ValueRef* {
auto evalResult = ScriptEvaluator::execute(context, [](ExecutionStateRef* state, ScriptRef* script) -> ValueRef* {
return script->execute(state);
},
scriptInitializeResult.script.get());

if (!evalResult.isSuccessful()) {
LOGD("Uncaught %s:\n", evalResult.resultOrErrorToString(context)->toStdUTF8String().data());
for (size_t i = 0; i < evalResult.stackTrace.size(); i++) {
LOGD("%s (%d:%d)\n", evalResult.stackTrace[i].srcName->toStdUTF8String().data(), (int)evalResult.stackTrace[i].loc.line, (int)evalResult.stackTrace[i].loc.column);
if (shouldPrintScriptResult) {
LOGD("Uncaught %s:\n", evalResult.resultOrErrorToString(context)->toStdUTF8String().data());
for (size_t i = 0; i < evalResult.stackTrace.size(); i++) {
LOGD("%s (%d:%d)\n", evalResult.stackTrace[i].srcName->toStdUTF8String().data(), (int)evalResult.stackTrace[i].loc.line, (int)evalResult.stackTrace[i].loc.column);
}
}
} else {
if (shouldPrintScriptResult) {
LOGD("%s", evalResult.resultOrErrorToString(context)->toStdUTF8String().data());
}
return evalResult;
}

if (shouldPrintScriptResult) {
LOGD("%s", evalResult.resultOrErrorToString(context)->toStdUTF8String().data());
}

if (shouldExecutePendingJobsAtEnd) {
Expand Down
3 changes: 2 additions & 1 deletion build/android/escargot/src/main/cpp/JNIFunctionObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Java_com_samsung_lwe_escargot_JavaScriptJavaCallbackFunctionObject_create(JNIEnv
return ValueRef::createUndefined();
}

ExecutionStateRefTracker tracker(state);
env->PushLocalFrame(32);
jobject callback = ensureScriptObjectExtraData(state->resolveCallee().get())->implementSideData;
auto callbackMethodId = env->GetMethodID(env->GetObjectClass(callback), "callback",
Expand Down Expand Up @@ -98,7 +99,7 @@ Java_com_samsung_lwe_escargot_JavaScriptJavaCallbackFunctionObject_create(JNIEnv
argumentCount,
isConstructor);

auto evaluatorResult = Evaluator::execute(contextRef->get(),
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(),
[](ExecutionStateRef* state, FunctionObjectRef::NativeFunctionInfo info) -> ValueRef* {
return FunctionObjectRef::create(state, info);
}, info);
Expand Down
6 changes: 3 additions & 3 deletions build/android/escargot/src/main/cpp/JNIGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Java_com_samsung_lwe_escargot_JavaScriptGlobalObject_jsonStringify(JNIEnv* env,
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
ValueRef* inputValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(input), input);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, GlobalObjectRef* globalObject, ValueRef* inputValueRef) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, GlobalObjectRef* globalObject, ValueRef* inputValueRef) -> ValueRef* {
return globalObject->jsonStringify()->call(state, globalObject->json(), 1, &inputValueRef);
}, globalObjectRef->get(), inputValueRef);

Expand All @@ -51,7 +51,7 @@ Java_com_samsung_lwe_escargot_JavaScriptGlobalObject_jsonParse(JNIEnv* env, jobj
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
ValueRef* inputValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(input), input);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, GlobalObjectRef* globalObject, ValueRef* inputValueRef) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, GlobalObjectRef* globalObject, ValueRef* inputValueRef) -> ValueRef* {
return globalObject->jsonParse()->call(state, globalObject->json(), 1, &inputValueRef);
}, globalObjectRef->get(), inputValueRef);

Expand All @@ -69,7 +69,7 @@ static jobject callPromiseBuiltinFunction(JNIEnv* env, jobject thiz, jobject con
auto contextRef = getPersistentPointerFromJava<ContextRef>(env, env->GetObjectClass(context), context);
ValueRef* iterableValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(iterable), iterable);

auto evaluatorResult = Evaluator::execute(contextRef->get(), closure, globalObjectRef->get(), iterableValueRef);
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), closure, globalObjectRef->get(), iterableValueRef);

return createOptionalValueFromEvaluatorJavaScriptValueResult(env, context, contextRef->get(),
evaluatorResult);
Expand Down
Loading

0 comments on commit 4f8582e

Please sign in to comment.