Skip to content

Commit

Permalink
leave error log per uncaught exception like a real JS platform
Browse files Browse the repository at this point in the history
  • Loading branch information
hmsk committed Nov 12, 2024
1 parent 8ef1150 commit ad652f6
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 3 deletions.
48 changes: 45 additions & 3 deletions ext/quickjsrb/quickjsrb.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
if (JS_IsError(ctx, j_val))
{
VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
if (!NIL_P(r_maybe_ruby_error)) {
if (!NIL_P(r_maybe_ruby_error))
{
return r_maybe_ruby_error;
}
// will support other errors like just returning an instance of Error
Expand Down Expand Up @@ -199,7 +200,8 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
if (JS_IsError(ctx, j_exceptionVal))
{
VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_exceptionVal);
if (!NIL_P(r_maybe_ruby_error)) {
if (!NIL_P(r_maybe_ruby_error))
{
JS_FreeValue(ctx, j_exceptionVal);
rb_exc_raise(r_maybe_ruby_error);
return Qnil;
Expand All @@ -211,8 +213,30 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
JSValue j_errorClassMessage = JS_GetPropertyStr(ctx, j_exceptionVal, "message");
const char *errorClassMessage = JS_ToCString(ctx, j_errorClassMessage);

JSValue j_stackTrace = JS_GetPropertyStr(ctx, j_exceptionVal, "stack");
const char *stackTrace = JS_ToCString(ctx, j_stackTrace);
const char *headlineTemplate = "Uncaught %s: %s\n%s";
int length = snprintf(NULL, 0, headlineTemplate, errorClassName, errorClassMessage, stackTrace);
char *headline = (char *)malloc(length + 1);
snprintf(headline, length + 1, headlineTemplate, errorClassName, errorClassMessage, stackTrace);

VMData *data = JS_GetContextOpaque(ctx);
VALUE r_log_class = rb_const_get(rb_const_get(rb_const_get(rb_cClass, rb_intern("Quickjs")), rb_intern("VM")), rb_intern("Log"));
VALUE r_log = rb_funcall(r_log_class, rb_intern("new"), 0);
rb_iv_set(r_log, "@severity", ID2SYM(rb_intern("error")));
VALUE r_row = rb_ary_new();
VALUE r_loghash = rb_hash_new();
rb_hash_aset(r_loghash, ID2SYM(rb_intern("raw")), rb_str_new2(headline));
rb_hash_aset(r_loghash, ID2SYM(rb_intern("c")), rb_str_new2(headline));
rb_ary_push(r_row, r_loghash);
rb_iv_set(r_log, "@row", r_row);
rb_ary_push(data->logs, r_log);

JS_FreeValue(ctx, j_errorClassMessage);
JS_FreeValue(ctx, j_errorClassName);
JS_FreeValue(ctx, j_stackTrace);
JS_FreeCString(ctx, stackTrace);
free(headline);

VALUE r_error_class, r_error_message = rb_str_new2(errorClassMessage);
if (is_native_error_name(errorClassName))
Expand Down Expand Up @@ -241,8 +265,26 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
else // exception without Error object
{
const char *errorMessage = JS_ToCString(ctx, j_exceptionVal);
VALUE r_error_message = rb_sprintf("%s", errorMessage);
const char *headlineTemplate = "Uncaught '%s'";
int length = snprintf(NULL, 0, headlineTemplate, errorMessage);
char *headline = (char *)malloc(length + 1);
snprintf(headline, length + 1, headlineTemplate, errorMessage);

VMData *data = JS_GetContextOpaque(ctx);
VALUE r_log_class = rb_const_get(rb_const_get(rb_const_get(rb_cClass, rb_intern("Quickjs")), rb_intern("VM")), rb_intern("Log"));
VALUE r_log = rb_funcall(r_log_class, rb_intern("new"), 0);
rb_iv_set(r_log, "@severity", ID2SYM(rb_intern("error")));
VALUE r_row = rb_ary_new();
VALUE r_loghash = rb_hash_new();
rb_hash_aset(r_loghash, ID2SYM(rb_intern("raw")), rb_str_new2(headline));
rb_hash_aset(r_loghash, ID2SYM(rb_intern("c")), rb_str_new2(headline));
rb_ary_push(r_row, r_loghash);
rb_iv_set(r_log, "@row", r_row);
rb_ary_push(data->logs, r_log);

free(headline);

VALUE r_error_message = rb_sprintf("%s", errorMessage);
JS_FreeCString(ctx, errorMessage);
JS_FreeValue(ctx, j_exceptionVal);
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
Expand Down
8 changes: 8 additions & 0 deletions test/fixture.esm.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export const member = () => "I am a exported member of ESM.";
export const defaultMember = () => "I am a default export of ESM.";
export default defaultMember;

const thrower = () => {
throw new Error("unpleasant wrapped error");
}

export const wrapError = () => {
thrower();
}
56 changes: 56 additions & 0 deletions test/quickjs_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -411,5 +411,61 @@ class ConsoleLoggers < QuickjsVmTest
assert_equal(@vm.logs.last.raw, ['log promise', 'Promise'])
end
end

class StackTraces < QuickjsVmTest
setup { @vm = Quickjs::VM.new }
teardown { @vm = nil }

test 'unhandled exception with an Error class should be logged with stack trace' do
assert_raises(Quickjs::ReferenceError) do
@vm.eval_code("
const a = 1;
const c = 3;
a + b;
")
end
assert_equal(@vm.logs.size, 1)
assert_equal(@vm.logs.last.severity, :error)
assert_equal(
@vm.logs.last.raw.first.split("\n"),
[
"Uncaught ReferenceError: 'b' is not defined",
' at <eval> (<code>:4)'
]
)
end

test 'unhandled exception without any Error class should be logged with stack trace' do
assert_raises(Quickjs::RuntimeError) do
@vm.eval_code("
const a = 1;
throw 'Don\\'t wanna compute at all';
")
end
assert_equal(@vm.logs.size, 1)
assert_equal(@vm.logs.last.severity, :error)
assert_equal(
@vm.logs.last.raw.first.split("\n"),
[
"Uncaught 'Don't wanna compute at all'"
]
)
end

test 'should include multi layers of stack trace' do
@vm.import(['wrapError'], from: File.read('./test/fixture.esm.js'))
assert_raises(Quickjs::RuntimeError) do
@vm.eval_code('wrapError();')
end
assert_equal(@vm.logs.size, 1)
assert_equal(@vm.logs.last.severity, :error)
trace = @vm.logs.last.raw.first.split("\n")
assert_equal(trace.size, 4)
assert_equal(trace[0], 'Uncaught Error: unpleasant wrapped error')
assert_match(/at thrower \(\w{12}:6\)/, trace[1])
assert_match(/at wrapError \(\w{12}:10\)/, trace[2])
assert_equal(' at <eval> (<code>)', trace[3])
end
end
end
end

0 comments on commit ad652f6

Please sign in to comment.