Skip to content

Commit

Permalink
Compute stacktrace when creating ErrorObject in Java
Browse files Browse the repository at this point in the history
Signed-off-by: Seonghyun Kim <[email protected]>
  • Loading branch information
ksh8281 committed Nov 6, 2023
1 parent e706ea6 commit b5b3b2b
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1837,12 +1837,15 @@ public Optional<JavaScriptValue> callback(Context context, Optional<JavaScriptVa
return null;
}
});
Evaluator.evalScript(context, "Native.throwsException()", "");
ret = Evaluator.evalScript(context, "Native.throwsException()", "");
ret = Evaluator.evalScript(context, "Native.throwsException()", "test.js");
assertFalse(ret.isPresent());
assertTrue(context.exceptionWasThrown());
JavaScriptErrorObject err = context.lastThrownException().get().asScriptErrorObject();
assertTrue(err.extraData().get() instanceof RuntimeException);
assertEquals(err.stack(context).get().toJavaString(),
"at test.js(1:1)\n" +
"Native.throwsException(\n" +
"^\n");

ArrayList<Object> thenCalled = new ArrayList<>();
JavaScriptPromiseObject promise = JavaScriptPromiseObject.create(context);
Expand Down
89 changes: 89 additions & 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 Expand Up @@ -346,4 +347,92 @@ ScriptObjectExtraData* ensureScriptObjectExtraData(ObjectRef* ref)

}
return data;
}


std::string ExecutionStateRefTracker::computeStacktrace()
{
auto iter = g_lastExecutionStateVector.rbegin();
std::string ret;
std::set<ExecutionStateRef*> seenRefs;
while (iter != g_lastExecutionStateVector.rend()) {
ExecutionStateRef* ref = *iter;
if (seenRefs.find(ref) == seenRefs.end()) {
GCManagedVector<Evaluator::StackTraceData> data = (*iter)->computeStackTrace();

for (size_t i = 0; i < data.size(); i++) {
ret += "at ";
ret += data[i].srcName->toStdUTF8String();
ret += "(";
ret += std::to_string(data[i].loc.line);
ret += ":";
ret += std::to_string(data[i].loc.column);
ret += ")\n";

Escargot::StringRef* src = data[i].sourceCode;
if (src->length()) {
const size_t preLineMax = 40;
const size_t afterLineMax = 40;

size_t preLineSoFar = 0;
size_t afterLineSoFar = 0;

size_t start = data[i].loc.index;
int64_t idx = (int64_t)start;
while (start - idx < preLineMax) {
if (idx == 0) {
break;
}
if (src->charAt((size_t)idx) == '\r' ||
src->charAt((size_t)idx) == '\n') {
idx++;
break;
}
idx--;
}
preLineSoFar = idx;

idx = start;
while (idx - start < afterLineMax) {
if ((size_t)idx == src->length() - 1) {
break;
}
if (src->charAt((size_t)idx) == '\r' ||
src->charAt((size_t)idx) == '\n') {
break;
}
idx++;
}
afterLineSoFar = idx;

if (preLineSoFar <= afterLineSoFar &&
preLineSoFar <= src->length() &&
afterLineSoFar <= src->length()) {
auto subSrc = src->substring(preLineSoFar, afterLineSoFar);
ret += subSrc->toStdUTF8String();
ret += "\n";
std::string sourceCodePosition;
for (size_t i = preLineSoFar; i < start; i++) {
sourceCodePosition += " ";
}
sourceCodePosition += "^\n";
ret += sourceCodePosition;
}
}
}

while (true) {
seenRefs.insert(ref);
if (ref->parent()) {
ref = ref->parent().get();
} else {
break;
}
}

}
iter++;
}

return ret;
}
32 changes: 32 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,34 @@ PersistentRefHolder<NativeType>* getPersistentPointerFromJava(JNIEnv *env, jclas
return pVMRef;
}

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

~ExecutionStateRefTracker()
{
g_lastExecutionStateVector.pop_back();
}

static std::string computeStacktrace();
private:
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...);
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
2 changes: 1 addition & 1 deletion 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 Down
41 changes: 38 additions & 3 deletions build/android/escargot/src/main/cpp/JNIErrorObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,46 @@ 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);
ErrorObjectRef* errorObject = ErrorObjectRef::create(state, code, jsMessage);
std::string stacktrace = ExecutionStateRefTracker::computeStacktrace();
errorObject->defineDataProperty(state, StringRef::createFromASCII("stack"),
ObjectRef::DataPropertyDescriptor(
StringRef::createFromUTF8(stacktrace.data(), stacktrace.size()),
ObjectRef::PresentAttribute::AllPresent));
return errorObject;
}, code, jsMessage);

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

ErrorObjectRef* object = evaluatorResult.result->asErrorObject();
ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state,
ErrorObjectRef* object) -> ValueRef* {
std::string stacktrace = ExecutionStateRefTracker::computeStacktrace();
object->defineDataProperty(state, StringRef::createFromASCII("stack"),
ObjectRef::DataPropertyDescriptor(
StringRef::createFromUTF8(stacktrace.data(), stacktrace.size()),
ObjectRef::PresentAttribute::AllPresent));
return object;
}, object);

return createJavaObjectFromValue(env, object);
}

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);
}
2 changes: 1 addition & 1 deletion build/android/escargot/src/main/cpp/JNIEvaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ 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());
Expand Down
2 changes: 1 addition & 1 deletion build/android/escargot/src/main/cpp/JNIFunctionObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,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
10 changes: 5 additions & 5 deletions build/android/escargot/src/main/cpp/JNIObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Java_com_samsung_lwe_escargot_JavaScriptObject_create(JNIEnv* env, jclass clazz,
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 ObjectRef::create(state);
});

Expand All @@ -46,7 +46,7 @@ Java_com_samsung_lwe_escargot_JavaScriptObject_get(JNIEnv* env, jobject thiz, jo
ObjectRef* thisValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(thiz), thiz)->asObject();
ValueRef* propertyNameValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(propertyName), propertyName);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef) -> ValueRef* {
return thisValueRef->get(state, propertyNameValueRef);
}, thisValueRef, propertyNameValueRef);

Expand All @@ -68,7 +68,7 @@ Java_com_samsung_lwe_escargot_JavaScriptObject_set(JNIEnv* env, jobject thiz, jo
ValueRef* propertyNameValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(propertyName), propertyName);
ValueRef* valueRef = unwrapValueRefFromValue(env, env->GetObjectClass(value), value);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef, ValueRef* valueRef) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef, ValueRef* valueRef) -> ValueRef* {
return ValueRef::create(thisValueRef->set(state, propertyNameValueRef, valueRef));
}, thisValueRef, propertyNameValueRef, valueRef);

Expand All @@ -95,7 +95,7 @@ Java_com_samsung_lwe_escargot_JavaScriptObject_defineDataProperty(JNIEnv* env, j
ValueRef* propertyNameValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(propertyName), propertyName);
ValueRef* valueRef = unwrapValueRefFromValue(env, env->GetObjectClass(value), value);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef, ValueRef* valueRef,
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef, ValueRef* valueRef,
jboolean isWritable,
jboolean isEnumerable,
jboolean isConfigurable) -> ValueRef* {
Expand All @@ -119,7 +119,7 @@ Java_com_samsung_lwe_escargot_JavaScriptObject_getOwnProperty(JNIEnv* env, jobje
ObjectRef* thisValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(thiz), thiz)->asObject();
ValueRef* propertyNameValueRef = unwrapValueRefFromValue(env, env->GetObjectClass(propertyName), propertyName);

auto evaluatorResult = Evaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef) -> ValueRef* {
auto evaluatorResult = ScriptEvaluator::execute(contextRef->get(), [](ExecutionStateRef* state, ObjectRef* thisValueRef, ValueRef* propertyNameValueRef) -> ValueRef* {
return thisValueRef->getOwnProperty(state, propertyNameValueRef);
}, thisValueRef, propertyNameValueRef);

Expand Down
Loading

0 comments on commit b5b3b2b

Please sign in to comment.