0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-25 23:14:13 +01:00

ircd::js: Improve the exception translation.

This commit is contained in:
Jason Volk 2016-10-19 19:50:55 -07:00
parent a8ee379ac2
commit b289c63b99
6 changed files with 175 additions and 99 deletions

View file

@ -327,6 +327,7 @@ RB_CHK_SYSHEADER([system_error], [SYSTEM_ERROR])
RB_CHK_SYSHEADER([map], [MAP])
RB_CHK_SYSHEADER([set], [SET])
RB_CHK_SYSHEADER([list], [LIST])
RB_CHK_SYSHEADER([stack], [STACK])
RB_CHK_SYSHEADER([deque], [DEQUE])
RB_CHK_SYSHEADER([array], [ARRAY])
RB_CHK_SYSHEADER([vector], [VECTOR])

View file

@ -42,8 +42,16 @@ struct context
{
size_t stack_chunk_size = 8_KiB;
bool dtor_gc = true;
}
opts; // We keep a copy of the given opts here.
};
struct exstate
{
JSExceptionState *state; // exstate
JSErrorReport report; // note: ptrs within are not necessarily safe
};
struct opts opts; // We keep a copy of the given opts here.
std::stack<struct exstate> exstate;
operator JSContext *() const { return get(); }
operator JSContext &() const { return custom_ptr<JSContext>::operator*(); }
@ -83,13 +91,17 @@ void priv(context &, privdata *const &);
inline auto version(const context &c) { return version(JS_GetVersion(c)); }
inline auto running(const context &c) { return JS_IsRunning(c); }
inline auto uncaught_exception(const context &c) { return JS_IsExceptionPending(c); }
inline auto rethrow_exception(context &c) { return JS_ReportPendingException(c); }
inline auto interrupted(const context &c) { return JS_CheckForInterrupt(c); }
inline void out_of_memory(context &c) { JS_ReportOutOfMemory(c); }
inline void allocation_overflow(context &c) { JS_ReportAllocationOverflow(c); }
inline void run_gc(context &c) { JS_MaybeGC(c); }
JSObject *current_global(context &c);
JSObject *current_global(); // thread_local
bool rethrow_exception(context &c);
void push_exception(context &c, const JSErrorReport &);
JSErrorReport pop_exception(context &c);
// thread_local
JSObject *current_global();
inline JSObject *
@ -98,6 +110,12 @@ current_global()
return current_global(*cx);
}
inline bool
rethrow_exception(context &c)
{
return JS_ReportPendingException(c);
}
inline JSObject *
current_global(context &c)
{

View file

@ -25,37 +25,22 @@
namespace ircd {
namespace js {
class error_handler
{
friend class runtime;
using closure = std::function<void (const char *const &msg, JSErrorReport &)>;
error_handler *theirs;
closure handler;
public:
error_handler(const closure &handler);
error_handler(closure &&handler);
~error_handler() noexcept;
};
struct jserror
:js::error
{
protected:
std::u16string msg;
JSErrorReport report;
IRCD_OVERLOAD(pending)
JS::PersistentRootedValue val;
void create(const JSErrorReport &);
void generate(const JSExnType &type, const char *const &fmt, va_list ap);
public:
JS::Value create_error(const JS::HandleObject &stack, const JS::HandleString &file, const std::pair<uint, uint> &linecol) const;
JS::Value create_error() const;
void set_pending() const;
jserror(pending_t);
jserror(generate_skip_t);
jserror(const JSErrorReport &);
jserror(const char *fmt = " ", ...) AFP(2, 3);
jserror(const JS::Value &);
};
#define IRCD_JS_ERROR_DEF(name, type) \

View file

@ -38,9 +38,6 @@ class runtime
static bool handle_context(JSContext *, uint op, void *) noexcept;
static bool handle_interrupt(JSContext *) noexcept;
friend struct error_handler;
struct error_handler *error_handler; // Error reports directed at handler
public:
struct opts
{

View file

@ -69,6 +69,7 @@ extern "C" {
#include <RB_INC_SYSTEM_ERROR
#include <RB_INC_ARRAY
#include <RB_INC_VECTOR
#include <RB_INC_STACK
#include <RB_INC_STRING
#include <RB_INC_LOCALE
#include <RB_INC_CODECVT

View file

@ -278,7 +278,7 @@ noexcept try
assert(&our(c) == cx);
auto &trap(from(obj));
trap.debug("has: %s", string(id).c_str());
trap.debug("has: '%s'", string(id).c_str());
*resolved = trap.on_has(*obj.get(), id.get());
return true;
}
@ -306,7 +306,7 @@ noexcept try
assert(&our(c) == cx);
auto &trap(from(obj));
trap.debug("del: %s", string(id).c_str());
trap.debug("del: '%s'", string(id).c_str());
if(trap.on_del(*obj.get(), id.get()))
res.succeed();
@ -336,7 +336,7 @@ noexcept try
assert(&our(c) == cx);
auto &trap(from(obj));
trap.debug("get: %s", string(id).c_str());
trap.debug("get: '%s'", string(id).c_str());
val.set(trap.on_get(*obj.get(), id.get(), val));
return true;
}
@ -365,7 +365,7 @@ noexcept try
assert(&our(c) == cx);
auto &trap(from(obj));
trap.debug("set: %s", string(id).c_str());
trap.debug("set: '%s'", string(id).c_str());
val.set(trap.on_set(*obj.get(), id.get(), val));
if(!val.isUndefined())
res.succeed();
@ -380,7 +380,7 @@ catch(const jserror &e)
catch(const std::exception &e)
{
auto &trap(from(obj));
trap.host_exception("get: '%s': %s",
trap.host_exception("set: '%s': %s",
string(id).c_str(),
e.what());
return false;
@ -396,7 +396,7 @@ noexcept try
assert(&our(c) == cx);
auto &trap(from(obj));
trap.debug("add: %s", string(id).c_str());
trap.debug("add: '%s'", string(id).c_str());
trap.on_add(*obj.get(), id.get(), val.get());
return true;
}
@ -597,7 +597,7 @@ const
{
value ret;
if(!JS_ExecuteScript(*cx, *this, &ret))
throw internal_error("Failed to execute script");
throw jserror(jserror::pending);
return ret;
}
@ -608,7 +608,7 @@ const
{
value ret;
if(!JS_ExecuteScript(*cx, stack, *this, &ret))
throw internal_error("Failed to execute script");
throw jserror(jserror::pending);
return ret;
}
@ -660,7 +660,7 @@ const
{
value ret;
if(!JS_CallFunction(*cx, that, *this, args, &ret))
throw internal_error("Failed to call Function");
throw jserror(jserror::pending);
return ret;
}
@ -677,7 +677,7 @@ ircd::js::call(const object &obj,
{
value ret;
if(!JS_CallFunction(*cx, obj, func, args, &ret))
throw internal_error("Failed to call function");
throw jserror(jserror::pending);
return ret;
}
@ -689,7 +689,7 @@ ircd::js::call(const object &obj,
{
value ret;
if(!JS_CallFunctionValue(*cx, obj, val, args, &ret))
throw internal_error("Failed to apply function value to object");
throw jserror(jserror::pending);
return ret;
}
@ -701,7 +701,7 @@ ircd::js::call(const object &obj,
{
value ret;
if(!JS_CallFunctionName(*cx, obj, name, args, &ret))
throw reference_error("Failed to call function \"%s\"", name);
throw jserror(jserror::pending);
return ret;
}
@ -821,35 +821,22 @@ ircd::js::native_size(const JSString *const &s)
// ircd/js/error.h
//
ircd::js::error_handler::error_handler(closure &&handler)
:theirs{rt->error_handler}
,handler{std::move(handler)}
ircd::js::jserror::jserror(const JS::Value &val)
:ircd::js::error{generate_skip}
,val{*cx, val}
{
rt->error_handler = this;
}
ircd::js::error_handler::error_handler(const closure &handler)
:theirs{rt->error_handler}
,handler{handler}
{
rt->error_handler = this;
}
ircd::js::error_handler::~error_handler()
noexcept
{
assert(rt->error_handler == this);
rt->error_handler = theirs;
}
ircd::js::jserror::jserror(generate_skip_t)
:ircd::js::error(generate_skip)
,val{*cx}
{
}
ircd::js::jserror::jserror(const char *const fmt,
...)
:ircd::js::error(generate_skip)
:ircd::js::error{generate_skip}
,val{*cx}
{
va_list ap;
va_start(ap, fmt);
@ -857,38 +844,62 @@ ircd::js::jserror::jserror(const char *const fmt,
va_end(ap);
}
ircd::js::jserror::jserror(const JSErrorReport &report)
:ircd::js::error{generate_skip}
,val{*cx}
{
create(report);
}
ircd::js::jserror::jserror(pending_t)
:ircd::js::error{generate_skip}
,val{*cx}
{
auto report(pop_exception(*cx));
if(report.flags & JSREPORT_EXCEPTION &&
report.errorNumber != 105)
{
JS::RootedObject obj(*cx, &val.toObject());
if(likely(JS_ErrorFromException(*cx, obj)))
report = *JS_ErrorFromException(*cx, obj);
const auto msg(report.ucmessage? string::convert(report.ucmessage) : std::string{});
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf), "%s%s%s",
reflect((JSExnType)report.exnType),
msg.empty()? "." : ": ",
msg.c_str());
return;
}
switch(report.errorNumber)
{
case 61: // JSAPI's code for interruption
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf),
"interrupted @ line: %u col: %u",
report.lineno,
report.column);
break;
case 105: // JSAPI's code for user reported error
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf),
"(BUG) Host exception");
break;
default:
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf),
"Unknown non-exception #%u flags[%02x]",
report.errorNumber,
report.flags);
break;
}
}
void
ircd::js::jserror::set_pending()
const
{
JS::RootedValue ex(*cx, create_error());
JS_SetPendingException(*cx, ex);
}
JS::Value
ircd::js::jserror::create_error(const JS::HandleObject &stack,
const JS::HandleString &file,
const std::pair<uint, uint> &linecol)
const
{
JS::RootedValue ret(*cx);
JS::RootedString msg(*cx);
const auto type((JSExnType)report.exnType);
const auto &line(linecol.first);
const auto &col(linecol.second);
if(!JS::CreateError(*cx, type, stack, file, line, col, const_cast<JSErrorReport *>(&report), msg, &ret))
throw error("Failed to construct jserror exception!");
return ret;
}
JS::Value
ircd::js::jserror::create_error()
const
{
JS::RootedObject stack(*cx);
JS::RootedString file(*cx);
return create_error(stack, file, {0, 0});
JS_SetPendingException(*cx, val);
}
void
@ -897,9 +908,37 @@ ircd::js::jserror::generate(const JSExnType &type,
va_list ap)
{
ircd::exception::generate(fmt, ap);
msg = string::convert(what());
const auto msg(string::convert(what()));
JSErrorReport report;
report.ucmessage = msg.c_str();
report.exnType = type;
create(report);
}
void
ircd::js::jserror::create(const JSErrorReport &report)
{
const auto type((JSExnType)report.exnType);
const auto &col(report.column);
const auto &line(report.lineno);
JS::RootedString msg(*cx);
JS::RootedString file(*cx);
JS::RootedObject stack(*cx);
if(!JS::CreateError(*cx,
type,
stack,
file,
line,
col,
const_cast<JSErrorReport *>(&report),
msg,
&val))
{
throw error("Failed to construct jserror exception!");
}
}
///////////////////////////////////////////////////////////////////////////////
@ -1155,6 +1194,7 @@ ircd::js::context::context(context &&other)
noexcept
:custom_ptr<JSContext>{std::move(other)}
,opts{std::move(other.opts)}
,exstate{std::move(other.exstate)}
{
// Branch not taken for null/defaulted instance of JSContext smart ptr
if(!!*this)
@ -1170,6 +1210,7 @@ noexcept
{
static_cast<custom_ptr<JSContext> &>(*this) = std::move(other);
opts = std::move(other.opts);
exstate = std::move(other.exstate);
// Branch not taken for null/defaulted instance of JSContext smart ptr
if(!!*this)
@ -1189,6 +1230,37 @@ noexcept
cx = nullptr;
}
JSErrorReport
ircd::js::pop_exception(context &c)
{
if(unlikely(c.exstate.empty()))
throw error("(internal error) No pending exception to restore");
auto &top(c.exstate.top());
JS_RestoreExceptionState(c, top.state);
const auto report(top.report);
c.exstate.pop();
return report;
}
void
ircd::js::push_exception(context &c,
const JSErrorReport &report)
{
c.exstate.push
({
JS_SaveExceptionState(c),
report
});
if(unlikely(!c.exstate.top().state))
{
c.exstate.pop();
throw error("(internal error) No pending exception to save");
}
}
ircd::js::context::lock::lock()
:lock{*cx}
{
@ -1217,7 +1289,6 @@ ircd::js::runtime::runtime(const struct opts &opts)
JS_NewRuntime(opts.maxbytes),
JS_DestroyRuntime
}
,error_handler{nullptr}
,opts{opts}
{
// We use their privdata to find `this` via our(JSRuntime*) function.
@ -1243,7 +1314,6 @@ ircd::js::runtime::runtime(const struct opts &opts)
ircd::js::runtime::runtime(runtime &&other)
noexcept
:custom_ptr<JSRuntime>{std::move(other)}
,error_handler{nullptr}
,opts{std::move(other.opts)}
{
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
@ -1259,7 +1329,6 @@ ircd::js::runtime::operator=(runtime &&other)
noexcept
{
static_cast<custom_ptr<JSRuntime> &>(*this) = std::move(other);
error_handler = std::move(other.error_handler);
opts = std::move(other.opts);
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
@ -1359,12 +1428,17 @@ ircd::js::runtime::handle_error(JSContext *const ctx,
JSErrorReport *const report)
noexcept
{
if(!rt->error_handler)
{
log.error("Unhandled: JSContext(%p): %s [%s]", (const void *)ctx, msg, debug(*report).c_str());
return;
}
assert(report);
rt->error_handler->handler(msg, *report);
/*
log.debug("JSContext(%p) Error report: %s | %s",
(const void *)ctx,
msg,
debug(*report).c_str());
log.critical("Unhandled: JSContext(%p): %s [%s]",
(const void *)ctx,
msg,
debug(*report).c_str());
*/
push_exception(our(ctx), *report);
}