diff --git a/src/quickjs/quickjs.c b/src/quickjs/quickjs.c index 67db7c625..773d29e4b 100644 --- a/src/quickjs/quickjs.c +++ b/src/quickjs/quickjs.c @@ -194,6 +194,7 @@ typedef enum JSErrorEnum { JS_INTERNAL_ERROR, JS_AGGREGATE_ERROR, JS_EXIT, /* used to close the vm down early */ + JS_INSTRUCTION_LIMIT, /* used when instruction limit is reached */ JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ } JSErrorEnum; @@ -6634,6 +6635,7 @@ JSValue JS_Exit(JSContext* ctx, const char* msg) JS_NewString(ctx, msg == 0 ? "JS_EXIT called" : msg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } + JS_SetUncatchableError(ctx, obj, TRUE); ret = JS_Throw(ctx, obj); return ret; } @@ -6778,6 +6780,23 @@ JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *c return val; } +JSValue JS_ThrowInstructionLimit(JSContext *ctx) +{ + JSValue obj, ret; + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[JS_INSTRUCTION_LIMIT], + JS_CLASS_ERROR); + if (unlikely(JS_IsException(obj))) { + obj = JS_NULL; + } else { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, + JS_NewString(ctx, "Instruction limit reached"), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + JS_SetUncatchableError(ctx, obj, TRUE); + ret = JS_Throw(ctx, obj); + return ret; +} + JSValue JS_ThrowOutOfMemory(JSContext *ctx) { JSRuntime *rt = ctx->rt; @@ -6863,8 +6882,7 @@ static inline __exception int js_poll_interrupts(JSContext *ctx) { /* XXX: should set a specific flag to avoid catching */ /* RHTODO: investigate if this is user-catchable. */ - JS_ThrowInternalError(ctx, "interrupted"); - JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); + JS_ThrowInstructionLimit(ctx); return -1; } @@ -52644,7 +52662,7 @@ void JS_EnableBignumExt(JSContext *ctx, BOOL enable) static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = { "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", - "InternalError", "AggregateError", "JSExit" + "InternalError", "AggregateError", "Exit", "InstructionLimit" }; /* Minimum amount of objects to be able to compile code and display diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index 4853bc5bc..2e5252b34 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -1591,54 +1591,80 @@ public: val = JS_Eval(vm.ctx, expr, expr_len, "", 0); + int normal_exit = 0; + + JSValue exception_val = JS_GetException(ctx); + if (!JS_IsUndefined(exception_val) && JS_IsError(ctx, exception_val)) { - JSValue exception_val = JS_GetException(ctx); - int is_error; + + int printed_something = 0; - is_error = JS_IsError(ctx, exception_val); + // Most exceptions should have a message field, try to fetch and print that + JSValue msg = JS_GetPropertyStr(ctx, exception_val, "message"); + if (!JS_IsUndefined(msg)) { - const char *str; - - str = JS_ToCString(ctx, exception_val); - if (str) + if (const char *str = JS_ToCString(ctx, msg); str) { - JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << str; + std::string m(str); JS_FreeCString(ctx, str); + + if ((m == "HookExit Accept" || m == "HookExit Rollback") && + (hookCtx.result.exitType == hook_api::ExitType::ACCEPT || + hookCtx.result.exitType == hook_api::ExitType::ROLLBACK)) + { + normal_exit = 1; + } + else + { + JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: JSException " << m; + printed_something++; + } } - else - { - JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: [exception]"; - } - } - if (is_error) { - JSValue val = JS_GetPropertyStr(ctx, exception_val, "stack"); - if (!JS_IsUndefined(val)) { - const char *str; + JS_FreeValue(ctx, msg); - str = JS_ToCString(ctx, val); - if (str) + // Accept/rollback are handled via an uncatchable exception internally in quickjs + // so only print a backtrace if it isn't a normal exit. + if (!normal_exit) + { + + if (!printed_something) + { + JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: [unknown exception]"; + } + + JSValue bt = JS_GetPropertyStr(ctx, exception_val, "stack"); + if (!normal_exit && !JS_IsUndefined(bt)) + { + if (const char *str = JS_ToCString(ctx, bt); str) { JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << str; JS_FreeCString(ctx, str); } - else - { - JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: [exception]"; - } } - JS_FreeValue(ctx, val); - } - JS_FreeValue(ctx, exception_val); - } + JS_FreeValue(ctx, bt); + } + } + JS_FreeValue(ctx, exception_val); + + if (normal_exit) + { + if (hookCtx.result.exitType == hook_api::ExitType::ACCEPT) + { + JLOG(j.warn()) << "HookInfo[" << HC_ACC() << "]: JSVM Exited with ACCEPT"; + } + else + { + JLOG(j.warn()) << "HookInfo[" << HC_ACC() << "]: JSVM Exited with ROLLBACK"; + } + } /* // RHTODO: place jsvm_error exit type logic appropriately hookCtx.result.exitType = hook_api::ExitType::JSVM_ERROR; hookCtx.result.instructionCount = 0; //? */ JS_FreeValue(ctx, val); - //JS_FreeValue(ctx, obj); } HookExecutorJS(HookContext& ctx)