2016-10-11 06:28:16 +02:00
|
|
|
/*
|
|
|
|
* charybdis: standing on the shoulders of giant build times
|
|
|
|
*
|
|
|
|
* Copyright (C) 2016 Charybdis Development Team
|
|
|
|
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice is present in all copies.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <js/Initialization.h> // JS_Init() / JS_ShutDown()
|
|
|
|
#include <ircd/js/js.h>
|
|
|
|
|
|
|
|
namespace ircd {
|
|
|
|
namespace js {
|
|
|
|
|
2016-10-23 03:46:27 +02:00
|
|
|
// Logging facility for this submodule with SNOMASK.
|
|
|
|
struct log::log log
|
|
|
|
{
|
|
|
|
"js", 'J'
|
|
|
|
};
|
|
|
|
|
2016-10-16 01:53:44 +02:00
|
|
|
// Location of the thread_local runtext. externs exist in js/runtime.h and js/context.h.
|
|
|
|
// If these are null, js is not available on your thread.
|
|
|
|
__thread runtime *rt;
|
|
|
|
__thread context *cx;
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
// Location of the main JSRuntime and JSContext instances.
|
|
|
|
runtime *main_runtime;
|
|
|
|
context *main_context;
|
2016-10-12 09:04:26 +02:00
|
|
|
|
2016-10-11 06:28:16 +02:00
|
|
|
} // namespace js
|
|
|
|
} // namespace ircd
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js.h - Without 3rd party (JSAPI) symbols
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::init::init()
|
|
|
|
{
|
|
|
|
log.info("Initializing the JS engine [%s: %s]",
|
|
|
|
"SpiderMonkey",
|
|
|
|
version(ver::IMPLEMENTATION));
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
const scope exit([this]
|
|
|
|
{
|
|
|
|
// Ensure ~init() is always safe to call at any intermediate state
|
|
|
|
if(std::current_exception())
|
|
|
|
this->~init();
|
|
|
|
});
|
|
|
|
|
2016-10-11 06:28:16 +02:00
|
|
|
if(!JS_Init())
|
|
|
|
throw error("JS_Init(): failure");
|
2016-10-12 09:04:26 +02:00
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
struct runtime::opts runtime_opts;
|
|
|
|
struct context::opts context_opts;
|
|
|
|
log.info("Initializing the main JS Runtime (main_maxbytes: %zu)",
|
|
|
|
runtime_opts.maxbytes);
|
2016-10-12 09:04:26 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
main_runtime = new runtime(runtime_opts);
|
|
|
|
main_context = new context(*main_runtime, context_opts);
|
2016-10-14 02:24:50 +02:00
|
|
|
log.info("Initialized main JS Runtime and context (version: '%s')",
|
2016-10-20 12:25:41 +02:00
|
|
|
version(*main_context));
|
2016-10-19 02:11:55 +02:00
|
|
|
|
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
const std::lock_guard<context> lock{*main_context};
|
2016-10-19 02:11:55 +02:00
|
|
|
ircd::mods::load("kernel");
|
|
|
|
}
|
|
|
|
}
|
2016-10-11 06:28:16 +02:00
|
|
|
|
|
|
|
ircd::js::init::~init()
|
|
|
|
noexcept
|
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
if(main_context && !!*main_context) try
|
2016-10-19 02:11:55 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
const std::lock_guard<context> lock{*main_context};
|
2016-10-19 02:11:55 +02:00
|
|
|
ircd::mods::unload("kernel");
|
|
|
|
}
|
2016-10-20 12:25:41 +02:00
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
log.warning("Failed to unload the kernel: %s", e.what());
|
|
|
|
}
|
2016-10-19 02:11:55 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
log.info("Terminating the JS Main Runtime");
|
|
|
|
delete main_context;
|
|
|
|
delete main_runtime;
|
2016-10-12 09:04:26 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
log.info("Terminating the JS Engine");
|
2016-10-11 06:28:16 +02:00
|
|
|
JS_ShutDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
ircd::js::version(const ver &type)
|
|
|
|
{
|
|
|
|
switch(type)
|
|
|
|
{
|
|
|
|
case ver::IMPLEMENTATION:
|
|
|
|
return JS_GetImplementationVersion();
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw error("version(): Unknown version type requested");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-19 10:44:35 +02:00
|
|
|
void
|
|
|
|
__attribute__((noreturn))
|
|
|
|
js::ReportOutOfMemory(ExclusiveContext *const c)
|
|
|
|
{
|
|
|
|
ircd::js::log.critical("jsalloc(): Reported out of memory (ExclusiveContext: %p)", (const void *)c);
|
|
|
|
std::terminate();
|
|
|
|
}
|
|
|
|
|
2016-10-11 06:28:16 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/js.h - With 3rd party (JSAPI) symbols
|
|
|
|
//
|
2016-10-13 04:58:48 +02:00
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/trap.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::trap::trap(std::string name,
|
|
|
|
const uint32_t &flags)
|
|
|
|
:_name{std::move(name)}
|
2016-10-23 03:46:27 +02:00
|
|
|
,_class{std::make_unique<JSClass>(JSClass
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
|
|
|
this->_name.c_str(),
|
|
|
|
flags,
|
2016-10-19 02:13:12 +02:00
|
|
|
handle_add, // flags & JSCLASS_GLOBAL_FLAGS? nullptr : handle_add,
|
2016-10-15 07:44:42 +02:00
|
|
|
handle_del,
|
|
|
|
handle_get,
|
|
|
|
handle_set,
|
|
|
|
handle_enu,
|
2016-10-16 08:28:14 +02:00
|
|
|
handle_has,
|
2016-10-15 07:44:42 +02:00
|
|
|
nullptr, // JSConvertOp - Obsolete since SpiderMonkey 44 // 45 = mayResolve?
|
|
|
|
handle_dtor,
|
|
|
|
handle_call,
|
|
|
|
handle_inst,
|
|
|
|
handle_ctor,
|
2016-10-19 02:13:12 +02:00
|
|
|
flags & JSCLASS_GLOBAL_FLAGS? JS_GlobalObjectTraceHook : handle_trace,
|
2016-10-15 07:44:42 +02:00
|
|
|
{ this } // reserved[0] TODO: ?????????
|
2016-10-23 03:46:27 +02:00
|
|
|
})}
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::trap::~trap()
|
|
|
|
noexcept
|
|
|
|
{
|
2016-10-16 09:12:57 +02:00
|
|
|
// Must run GC here to force reclamation of objects before
|
|
|
|
// the JSClass hosted by this trap destructs.
|
|
|
|
run_gc(*rt);
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
ircd::js::object
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::operator()()
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-23 03:46:27 +02:00
|
|
|
return { _class.get() };
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
ircd::js::object
|
|
|
|
ircd::js::trap::operator()(const object &proto)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-23 03:46:27 +02:00
|
|
|
return object { _class.get(), proto };
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::trap::handle_dtor(JSFreeOp *const op,
|
|
|
|
JSObject *const obj)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-18 03:43:23 +02:00
|
|
|
auto &trap(from(*obj));
|
|
|
|
trap.debug("dtor");
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(*obj));
|
|
|
|
trap.host_exception("dtor: %s", e.what());
|
|
|
|
return;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_ctor(JSContext *const c,
|
|
|
|
unsigned argc,
|
|
|
|
JS::Value *const argv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-23 08:13:52 +02:00
|
|
|
auto ca(JS::CallArgsFromVp(argc, argv));
|
|
|
|
object that(ca.callee());
|
|
|
|
|
|
|
|
auto &trap(from(that));
|
|
|
|
trap.debug("ctor: '%s'", trap.name().c_str());
|
|
|
|
|
|
|
|
object ret(JS_NewObjectForConstructor(*cx, &trap.jsclass(), ca));
|
|
|
|
trap.on_ctor(ret, ca);
|
|
|
|
ca.rval().set(ret);
|
|
|
|
return true;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
2016-10-18 03:43:23 +02:00
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
2016-10-23 08:13:52 +02:00
|
|
|
auto ca(JS::CallArgsFromVp(argc, argv));
|
|
|
|
auto &trap(from(object(ca.callee())));
|
|
|
|
trap.host_exception("ctor: %s", e.what());
|
2016-10-18 03:43:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_call(JSContext *const c,
|
|
|
|
unsigned argc,
|
|
|
|
JS::Value *const argv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-23 08:13:52 +02:00
|
|
|
auto ca(JS::CallArgsFromVp(argc, argv));
|
|
|
|
object that(ca.computeThis(c));
|
|
|
|
object func(ca.callee());
|
|
|
|
|
|
|
|
auto &trap_that(from(that));
|
|
|
|
auto &trap_func(from(func));
|
|
|
|
|
|
|
|
trap_that.debug("call: '%s'", trap_func.name().c_str());
|
|
|
|
trap_func.debug("call");
|
|
|
|
|
|
|
|
ca.rval().set(trap_func.on_call(*func.get(), ca));
|
|
|
|
return true;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
2016-10-18 03:43:23 +02:00
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
2016-10-23 08:13:52 +02:00
|
|
|
auto ca(JS::CallArgsFromVp(argc, argv));
|
|
|
|
auto &trap(from(object(ca.computeThis(c))));
|
|
|
|
trap.host_exception("call: %s", e.what());
|
2016-10-18 03:43:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_enu(JSContext *const c,
|
|
|
|
JS::HandleObject obj)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-16 08:28:14 +02:00
|
|
|
trap.debug("enu");
|
2016-10-16 01:53:44 +02:00
|
|
|
return trap.on_enu(*obj.get());
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
2016-10-18 03:43:23 +02:00
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("enu: %s", e.what());
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
bool
|
2016-10-16 08:28:14 +02:00
|
|
|
ircd::js::trap::handle_has(JSContext *const c,
|
2016-10-15 07:44:42 +02:00
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::HandleId id,
|
|
|
|
bool *const resolved)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.debug("has: '%s'", string(id).c_str());
|
2016-10-18 03:43:23 +02:00
|
|
|
*resolved = trap.on_has(*obj.get(), id.get());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("has: '%s': %s",
|
|
|
|
string(id).c_str(),
|
|
|
|
e.what());
|
|
|
|
return false;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_del(JSContext *const c,
|
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::HandleId id,
|
|
|
|
JS::ObjectOpResult &res)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.debug("del: '%s'", string(id).c_str());
|
2016-10-18 03:43:23 +02:00
|
|
|
if(trap.on_del(*obj.get(), id.get()))
|
|
|
|
res.succeed();
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2016-10-18 03:43:23 +02:00
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("del '%s': %s",
|
|
|
|
string(id).c_str(),
|
|
|
|
e.what());
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_get(JSContext *const c,
|
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::HandleId id,
|
|
|
|
JS::MutableHandleValue val)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.debug("get: '%s'", string(id).c_str());
|
2016-10-18 03:43:23 +02:00
|
|
|
val.set(trap.on_get(*obj.get(), id.get(), val));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("get: '%s': %s",
|
|
|
|
string(id).c_str(),
|
|
|
|
e.what());
|
|
|
|
return false;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_set(JSContext *const c,
|
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::HandleId id,
|
|
|
|
JS::MutableHandleValue val,
|
|
|
|
JS::ObjectOpResult &res)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.debug("set: '%s'", string(id).c_str());
|
2016-10-18 03:43:23 +02:00
|
|
|
val.set(trap.on_set(*obj.get(), id.get(), val));
|
|
|
|
if(!val.isUndefined())
|
|
|
|
res.succeed();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.host_exception("set: '%s': %s",
|
2016-10-18 03:43:23 +02:00
|
|
|
string(id).c_str(),
|
|
|
|
e.what());
|
|
|
|
return false;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_add(JSContext *const c,
|
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::HandleId id,
|
|
|
|
JS::HandleValue val)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
auto &trap(from(obj));
|
2016-10-20 04:50:55 +02:00
|
|
|
trap.debug("add: '%s'", string(id).c_str());
|
2016-10-18 03:43:23 +02:00
|
|
|
trap.on_add(*obj.get(), id.get(), val.get());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("add: '%s': %s",
|
|
|
|
string(id).c_str(),
|
|
|
|
e.what());
|
|
|
|
return false;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::trap::handle_inst(JSContext *const c,
|
|
|
|
JS::HandleObject obj,
|
|
|
|
JS::MutableHandleValue val,
|
|
|
|
bool *const has_instance)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(c) == cx);
|
|
|
|
|
2016-10-16 08:28:14 +02:00
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.debug("inst");
|
|
|
|
|
2016-10-15 07:44:42 +02:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-18 03:43:23 +02:00
|
|
|
catch(const jserror &e)
|
|
|
|
{
|
|
|
|
e.set_pending();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(obj));
|
|
|
|
trap.host_exception("inst: %s", e.what());
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-15 07:44:42 +02:00
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::trap::handle_trace(JSTracer *const tracer,
|
|
|
|
JSObject *const obj)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept try
|
|
|
|
{
|
|
|
|
auto &trap(from(*obj));
|
|
|
|
trap.debug("trace");
|
|
|
|
}
|
|
|
|
catch(const jserror &e)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-18 03:43:23 +02:00
|
|
|
e.set_pending();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
auto &trap(from(*obj));
|
|
|
|
trap.host_exception("trace: %s", e.what());
|
|
|
|
return;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::trap &
|
|
|
|
ircd::js::trap::from(const JS::HandleObject &o)
|
|
|
|
{
|
|
|
|
return from(*o.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::trap &
|
|
|
|
ircd::js::trap::from(const JSObject &o)
|
|
|
|
{
|
|
|
|
auto *const c(JS_GetClass(const_cast<JSObject *>(&o)));
|
|
|
|
if(!c)
|
|
|
|
std::terminate(); //TODO: exception
|
|
|
|
|
|
|
|
if(!c->reserved[0])
|
|
|
|
std::terminate(); //TODO: exception
|
|
|
|
|
|
|
|
return *static_cast<trap *>(c->reserved[0]); //TODO: ???
|
|
|
|
}
|
|
|
|
|
2016-10-16 08:28:14 +02:00
|
|
|
void
|
|
|
|
ircd::js::trap::debug(const char *const fmt,
|
|
|
|
...)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
|
|
|
char buf[1024];
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
|
|
log.debug("trap(%p) \"%s\": %s",
|
|
|
|
reinterpret_cast<const void *>(this),
|
|
|
|
name().c_str(),
|
|
|
|
buf);
|
|
|
|
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2016-10-17 21:16:30 +02:00
|
|
|
void
|
|
|
|
ircd::js::trap::host_exception(const char *const fmt,
|
|
|
|
...)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
|
|
|
char buf[1024];
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
|
|
log.error("trap(%p) \"%s\": %s",
|
|
|
|
reinterpret_cast<const void *>(this),
|
|
|
|
name().c_str(),
|
|
|
|
buf);
|
|
|
|
|
|
|
|
JS_ReportError(*cx, "BUG: trap(%p) \"%s\" %s",
|
|
|
|
reinterpret_cast<const void *>(this),
|
|
|
|
name().c_str(),
|
|
|
|
buf);
|
|
|
|
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2016-10-23 08:13:52 +02:00
|
|
|
void
|
|
|
|
ircd::js::trap::on_ctor(object &obj,
|
|
|
|
const JS::CallArgs &)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-10-23 08:13:52 +02:00
|
|
|
ircd::js::value
|
|
|
|
ircd::js::trap::on_call(const JSObject &,
|
|
|
|
const JS::CallArgs &)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-23 08:13:52 +02:00
|
|
|
return {};
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::on_enu(const JSObject &obj)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-10-16 08:28:14 +02:00
|
|
|
ircd::js::trap::on_has(const JSObject &obj,
|
2016-10-18 03:43:23 +02:00
|
|
|
const jsid &id)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::on_del(const JSObject &obj,
|
2016-10-15 07:44:42 +02:00
|
|
|
const jsid &id)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-18 03:43:23 +02:00
|
|
|
JS::Value
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::on_get(const JSObject &obj,
|
2016-10-15 07:44:42 +02:00
|
|
|
const jsid &id,
|
2016-10-18 03:43:23 +02:00
|
|
|
const JS::Value &val)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-18 03:43:23 +02:00
|
|
|
return val;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-18 03:43:23 +02:00
|
|
|
JS::Value
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::on_set(const JSObject &obj,
|
2016-10-15 07:44:42 +02:00
|
|
|
const jsid &id,
|
2016-10-18 03:43:23 +02:00
|
|
|
const JS::Value &val)
|
2016-10-15 07:44:42 +02:00
|
|
|
{
|
2016-10-18 03:43:23 +02:00
|
|
|
return val;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-18 03:43:23 +02:00
|
|
|
JS::Value
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::trap::on_add(const JSObject &obj,
|
2016-10-15 07:44:42 +02:00
|
|
|
const jsid &id,
|
|
|
|
const JS::Value &val)
|
|
|
|
{
|
2016-10-18 03:43:23 +02:00
|
|
|
return val;
|
2016-10-15 07:44:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 03:00:25 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/script.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::script::script(const JS::CompileOptions &opts,
|
|
|
|
const std::string &src)
|
|
|
|
:JS::Rooted<JSScript *>{*cx}
|
|
|
|
{
|
|
|
|
if(!JS::Compile(*cx, opts, src.data(), src.size(), &(*this)))
|
|
|
|
throw syntax_error("Failed to compile script");
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::script::operator()()
|
|
|
|
const
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_ExecuteScript(*cx, *this, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::script::operator()(JS::AutoObjectVector &stack)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_ExecuteScript(*cx, stack, *this, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/function.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::function::function(JS::AutoObjectVector &stack,
|
|
|
|
const JS::CompileOptions &opts,
|
|
|
|
const char *const &name,
|
|
|
|
const std::vector<std::string> &args,
|
|
|
|
const std::string &src)
|
|
|
|
:JS::Rooted<JSFunction *>{*cx}
|
|
|
|
{
|
|
|
|
std::vector<const char *> argp(args.size());
|
|
|
|
std::transform(begin(args), end(args), begin(argp), []
|
|
|
|
(const std::string &arg)
|
|
|
|
{
|
|
|
|
return arg.data();
|
|
|
|
});
|
|
|
|
|
|
|
|
if(!JS::CompileFunction(*cx,
|
|
|
|
stack,
|
|
|
|
opts,
|
|
|
|
name,
|
|
|
|
argp.size(),
|
|
|
|
&argp.front(),
|
|
|
|
src.data(),
|
|
|
|
src.size(),
|
|
|
|
&(*this)))
|
|
|
|
{
|
|
|
|
throw syntax_error("Failed to compile function");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-19 03:00:25 +02:00
|
|
|
ircd::js::value
|
|
|
|
ircd::js::function::operator()(const object &that)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
return operator()(that, JS::HandleValueArray::empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::function::operator()(const object &that,
|
|
|
|
const JS::HandleValueArray &args)
|
|
|
|
const
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_CallFunction(*cx, that, *this, args, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/call.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::call(const object &obj,
|
|
|
|
const JS::HandleFunction &func,
|
|
|
|
const JS::HandleValueArray &args)
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_CallFunction(*cx, obj, func, args, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::call(const object &obj,
|
|
|
|
const JS::HandleValue &val,
|
|
|
|
const JS::HandleValueArray &args)
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_CallFunctionValue(*cx, obj, val, args, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::call(const object &obj,
|
|
|
|
const char *const &name,
|
|
|
|
const JS::HandleValueArray &args)
|
|
|
|
{
|
|
|
|
value ret;
|
|
|
|
if(!JS_CallFunctionName(*cx, obj, name, args, &ret))
|
2016-10-20 04:50:55 +02:00
|
|
|
throw jserror(jserror::pending);
|
2016-10-19 03:00:25 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::value
|
|
|
|
ircd::js::call(const object &obj,
|
|
|
|
const std::string &name,
|
|
|
|
const JS::HandleValueArray &args)
|
|
|
|
{
|
|
|
|
return call(obj, name.c_str(), args);
|
|
|
|
}
|
|
|
|
|
2016-10-17 21:14:35 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/string.h
|
|
|
|
//
|
|
|
|
|
2016-10-20 02:05:58 +02:00
|
|
|
const size_t ircd::js::string::CBUFSZ
|
|
|
|
{
|
|
|
|
1024
|
|
|
|
};
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
const char *
|
|
|
|
ircd::js::string::c_str()
|
|
|
|
const
|
|
|
|
{
|
|
|
|
static thread_local char cbuf[CBUFS][CBUFSZ];
|
|
|
|
static thread_local size_t ctr;
|
|
|
|
|
|
|
|
char *const buf(cbuf[ctr]);
|
2016-10-20 02:05:58 +02:00
|
|
|
native(get(), buf, CBUFSZ);
|
2016-10-19 02:17:29 +02:00
|
|
|
ctr = (ctr + 1) % CBUFS;
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::u16string
|
|
|
|
ircd::js::string::convert(const std::string &s)
|
2016-10-17 21:14:35 +02:00
|
|
|
{
|
|
|
|
static std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> converter;
|
|
|
|
|
2016-10-20 02:05:58 +02:00
|
|
|
return converter.from_bytes(s);
|
2016-10-17 21:14:35 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
std::u16string
|
|
|
|
ircd::js::string::convert(const char *const &s)
|
2016-10-17 21:14:35 +02:00
|
|
|
{
|
|
|
|
static std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> converter;
|
|
|
|
|
2016-10-20 02:05:58 +02:00
|
|
|
return s? converter.from_bytes(s) : std::u16string{};
|
2016-10-17 21:14:35 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
std::string
|
|
|
|
ircd::js::string::convert(const std::u16string &s)
|
2016-10-17 21:14:35 +02:00
|
|
|
{
|
|
|
|
static std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> converter;
|
|
|
|
|
2016-10-20 02:05:58 +02:00
|
|
|
return converter.to_bytes(s);
|
2016-10-17 21:14:35 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 02:17:29 +02:00
|
|
|
std::string
|
|
|
|
ircd::js::string::convert(const char16_t *const &s)
|
2016-10-17 21:14:35 +02:00
|
|
|
{
|
|
|
|
static std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> converter;
|
|
|
|
|
2016-10-20 02:05:58 +02:00
|
|
|
return s? converter.to_bytes(s) : std::string{};
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
ircd::js::string::convert(const char16_t *const &str,
|
|
|
|
char *const &buf,
|
|
|
|
const size_t &max)
|
|
|
|
{
|
|
|
|
static std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> converter;
|
|
|
|
|
|
|
|
//TODO: optimize
|
|
|
|
const auto s(converter.to_bytes(str));
|
|
|
|
return rb_strlcpy(buf, s.c_str(), max);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/value.h
|
|
|
|
//
|
|
|
|
|
|
|
|
std::string
|
|
|
|
ircd::js::native(const JSString *const &s)
|
|
|
|
{
|
|
|
|
std::string ret(native_size(s) + 1, char());
|
|
|
|
native(s, &ret.front(), ret.size());
|
|
|
|
ret.resize(ret.size() - 1);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
ircd::js::native(const JSString *const &s,
|
|
|
|
char *const &buf,
|
|
|
|
const size_t &max)
|
|
|
|
{
|
|
|
|
if(unlikely(!max))
|
|
|
|
return 0;
|
|
|
|
|
2016-10-22 09:42:10 +02:00
|
|
|
ssize_t ret(s? JS_EncodeStringToBuffer(*cx, const_cast<JSString *>(s), buf, max) : 0);
|
2016-10-20 02:05:58 +02:00
|
|
|
ret = std::max(ret, ssize_t(0));
|
|
|
|
ret = std::min(ret, ssize_t(max - 1));
|
|
|
|
buf[ret] = '\0';
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
ircd::js::native_size(const JSString *const &s)
|
|
|
|
{
|
|
|
|
return JS_GetStringEncodingLength(*cx, const_cast<JSString *>(s));
|
2016-10-17 21:14:35 +02:00
|
|
|
}
|
|
|
|
|
2016-10-17 21:16:30 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/error.h
|
|
|
|
//
|
|
|
|
|
2016-10-20 04:50:55 +02:00
|
|
|
ircd::js::jserror::jserror(const JS::Value &val)
|
|
|
|
:ircd::js::error{generate_skip}
|
|
|
|
,val{*cx, val}
|
2016-10-17 21:16:30 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::jserror::jserror(generate_skip_t)
|
|
|
|
:ircd::js::error(generate_skip)
|
2016-10-20 04:50:55 +02:00
|
|
|
,val{*cx}
|
2016-10-17 21:16:30 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::jserror::jserror(const char *const fmt,
|
|
|
|
...)
|
2016-10-20 04:50:55 +02:00
|
|
|
:ircd::js::error{generate_skip}
|
|
|
|
,val{*cx}
|
2016-10-17 21:16:30 +02:00
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
generate(JSEXN_ERR, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2016-10-20 04:50:55 +02:00
|
|
|
ircd::js::jserror::jserror(const JSErrorReport &report)
|
|
|
|
:ircd::js::error{generate_skip}
|
|
|
|
,val{*cx}
|
2016-10-17 21:16:30 +02:00
|
|
|
{
|
2016-10-20 04:50:55 +02:00
|
|
|
create(report);
|
2016-10-17 21:16:30 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 04:50:55 +02:00
|
|
|
ircd::js::jserror::jserror(pending_t)
|
|
|
|
:ircd::js::error{generate_skip}
|
|
|
|
,val{*cx}
|
2016-10-17 21:16:30 +02:00
|
|
|
{
|
2016-10-22 07:31:30 +02:00
|
|
|
if(unlikely(!restore_exception(*cx)))
|
|
|
|
{
|
|
|
|
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf),
|
|
|
|
"(internal error): Failed to restore exception.");
|
2016-10-20 12:25:41 +02:00
|
|
|
return;
|
2016-10-22 07:31:30 +02:00
|
|
|
}
|
2016-10-20 12:25:41 +02:00
|
|
|
|
|
|
|
auto &report(cx->report);
|
2016-10-22 07:31:30 +02:00
|
|
|
if(JS_GetPendingException(*cx, &val))
|
2016-10-20 04:50:55 +02:00
|
|
|
{
|
|
|
|
JS::RootedObject obj(*cx, &val.toObject());
|
|
|
|
if(likely(JS_ErrorFromException(*cx, obj)))
|
|
|
|
report = *JS_ErrorFromException(*cx, obj);
|
2016-10-17 21:16:30 +02:00
|
|
|
|
2016-10-22 07:31:30 +02:00
|
|
|
char linebuf[64];
|
|
|
|
snprintf(linebuf, sizeof(linebuf), "@%u+%u: ",
|
|
|
|
report.lineno,
|
|
|
|
report.column);
|
|
|
|
|
2016-10-20 04:50:55 +02:00
|
|
|
const auto msg(report.ucmessage? string::convert(report.ucmessage) : std::string{});
|
2016-10-22 07:31:30 +02:00
|
|
|
snprintf(ircd::exception::buf, sizeof(ircd::exception::buf), "%s%s%s%s",
|
2016-10-20 04:50:55 +02:00
|
|
|
reflect((JSExnType)report.exnType),
|
|
|
|
msg.empty()? "." : ": ",
|
2016-10-22 07:31:30 +02:00
|
|
|
msg.empty()? "" : linebuf,
|
2016-10-20 04:50:55 +02:00
|
|
|
msg.c_str());
|
|
|
|
|
2016-10-22 07:31:30 +02:00
|
|
|
JS_ClearPendingException(*cx);
|
2016-10-20 04:50:55 +02:00
|
|
|
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;
|
|
|
|
}
|
2016-10-17 21:16:30 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 04:50:55 +02:00
|
|
|
void
|
|
|
|
ircd::js::jserror::set_pending()
|
2016-10-17 21:16:30 +02:00
|
|
|
const
|
|
|
|
{
|
2016-10-20 04:50:55 +02:00
|
|
|
JS_SetPendingException(*cx, val);
|
2016-10-17 21:16:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::jserror::generate(const JSExnType &type,
|
|
|
|
const char *const &fmt,
|
|
|
|
va_list ap)
|
|
|
|
{
|
|
|
|
ircd::exception::generate(fmt, ap);
|
2016-10-20 04:50:55 +02:00
|
|
|
const auto msg(string::convert(what()));
|
|
|
|
|
|
|
|
JSErrorReport report;
|
2016-10-17 21:16:30 +02:00
|
|
|
report.ucmessage = msg.c_str();
|
|
|
|
report.exnType = type;
|
2016-10-20 04:50:55 +02:00
|
|
|
|
|
|
|
create(report);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::jserror::create(const JSErrorReport &report)
|
|
|
|
{
|
2016-10-22 07:31:30 +02:00
|
|
|
JSErrorReport cpy(report);
|
|
|
|
create(cpy);
|
|
|
|
}
|
2016-10-20 04:50:55 +02:00
|
|
|
|
2016-10-22 07:31:30 +02:00
|
|
|
void
|
|
|
|
ircd::js::jserror::create(JSErrorReport &report)
|
|
|
|
{
|
|
|
|
JS::AutoFilename fn;
|
|
|
|
const auto col(report.column? nullptr : &report.column);
|
|
|
|
const auto line(report.lineno? nullptr : &report.lineno);
|
|
|
|
DescribeScriptedCaller(*cx, &fn, line, col);
|
|
|
|
|
|
|
|
JS::RootedString msg
|
|
|
|
{
|
|
|
|
*cx,
|
|
|
|
JS_NewUCStringCopyZ(*cx, report.ucmessage)
|
|
|
|
};
|
|
|
|
|
|
|
|
JS::RootedString file
|
|
|
|
{
|
|
|
|
*cx,
|
|
|
|
JS_NewStringCopyZ(*cx, fn.get()?: "<unknown>")
|
|
|
|
};
|
|
|
|
|
|
|
|
JS::RootedObject stack(*cx, nullptr);
|
|
|
|
const auto type((JSExnType)report.exnType);
|
2016-10-20 04:50:55 +02:00
|
|
|
if(!JS::CreateError(*cx,
|
|
|
|
type,
|
|
|
|
stack,
|
|
|
|
file,
|
2016-10-22 07:31:30 +02:00
|
|
|
report.lineno,
|
|
|
|
report.column,
|
|
|
|
&report,
|
2016-10-20 04:50:55 +02:00
|
|
|
msg,
|
|
|
|
&val))
|
|
|
|
{
|
|
|
|
throw error("Failed to construct jserror exception!");
|
|
|
|
}
|
2016-10-17 21:16:30 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 03:00:12 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/debug.h
|
|
|
|
//
|
|
|
|
|
2016-10-22 09:34:12 +02:00
|
|
|
void
|
|
|
|
ircd::js::debug_log_gcparams()
|
|
|
|
{
|
|
|
|
for(int i(0); i < 50; ++i)
|
|
|
|
{
|
|
|
|
const auto key(static_cast<JSGCParamKey>(i));
|
|
|
|
const char *const name(reflect(key));
|
|
|
|
if(!strlen(name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// These trigger assertion failures
|
|
|
|
switch(key)
|
|
|
|
{
|
|
|
|
case JSGC_NUMBER:
|
|
|
|
case JSGC_MAX_CODE_CACHE_BYTES:
|
|
|
|
case JSGC_DECOMMIT_THRESHOLD:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
log.debug("context(%p) %s => %u",
|
|
|
|
(const void *)cx,
|
|
|
|
name,
|
|
|
|
get(*cx, key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 03:00:12 +02:00
|
|
|
std::string
|
|
|
|
ircd::js::debug(const JSErrorReport &r)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
if(JSREPORT_IS_WARNING(r.flags))
|
|
|
|
ss << "WARNING ";
|
|
|
|
|
|
|
|
if(JSREPORT_IS_EXCEPTION(r.flags))
|
|
|
|
ss << "EXCEPTION ";
|
|
|
|
|
|
|
|
if(JSREPORT_IS_STRICT(r.flags))
|
|
|
|
ss << "STRICT ";
|
|
|
|
|
|
|
|
if(JSREPORT_IS_STRICT_MODE_ERROR(r.flags))
|
|
|
|
ss << "STRICT_MODE_ERROR ";
|
|
|
|
|
|
|
|
if(r.isMuted)
|
|
|
|
ss << "MUTED ";
|
|
|
|
|
|
|
|
if(r.filename)
|
|
|
|
ss << "file[" << r.filename << "] ";
|
|
|
|
|
|
|
|
if(r.lineno)
|
|
|
|
ss << "line[" << r.lineno << "] ";
|
|
|
|
|
|
|
|
if(r.column)
|
|
|
|
ss << "col[" << r.column << "] ";
|
|
|
|
|
|
|
|
if(r.linebuf())
|
|
|
|
ss << "code[" << r.linebuf() << "] ";
|
|
|
|
|
|
|
|
//if(r.tokenptr)
|
|
|
|
// ss << "toke[" << r.tokenptr << "] ";
|
|
|
|
|
|
|
|
if(r.errorNumber)
|
|
|
|
ss << "errnum[" << r.errorNumber << "] ";
|
|
|
|
|
|
|
|
if(r.exnType)
|
|
|
|
ss << reflect(JSExnType(r.exnType)) << " ";
|
|
|
|
|
2016-10-17 21:14:35 +02:00
|
|
|
if(r.ucmessage)
|
2016-10-19 02:17:29 +02:00
|
|
|
ss << "\"" << string::convert(r.ucmessage) << "\" ";
|
2016-10-15 03:00:12 +02:00
|
|
|
|
2016-10-17 21:14:35 +02:00
|
|
|
for(auto it(r.messageArgs); it && *it; ++it)
|
2016-10-19 02:17:29 +02:00
|
|
|
ss << "\"" << string::convert(*it) << "\" ";
|
2016-10-15 03:00:12 +02:00
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
ircd::js::debug(const JS::HandleObject &o)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
if(JS_IsGlobalObject(o)) ss << "Global ";
|
|
|
|
if(JS_IsNative(o)) ss << "Native ";
|
|
|
|
if(JS::IsCallable(o)) ss << "Callable ";
|
|
|
|
if(JS::IsConstructor(o)) ss << "Constructor ";
|
|
|
|
|
|
|
|
bool ret;
|
|
|
|
if(JS_IsExtensible(*cx, o, &ret) && ret)
|
|
|
|
ss << "Extensible ";
|
|
|
|
|
|
|
|
if(JS_IsArrayObject(*cx, o, &ret) && ret)
|
|
|
|
ss << "Array ";
|
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
ircd::js::debug(const JS::Value &v)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
if(v.isNull()) ss << "Null ";
|
|
|
|
if(v.isUndefined()) ss << "Undefined ";
|
|
|
|
if(v.isBoolean()) ss << "Boolean ";
|
|
|
|
if(v.isTrue()) ss << "TrueValue ";
|
|
|
|
if(v.isFalse()) ss << "FalseValue ";
|
|
|
|
if(v.isNumber()) ss << "Number ";
|
|
|
|
if(v.isDouble()) ss << "Double ";
|
|
|
|
if(v.isInt32()) ss << "Int32 ";
|
|
|
|
if(v.isString()) ss << "String ";
|
|
|
|
if(v.isObject()) ss << "Object ";
|
|
|
|
if(v.isSymbol()) ss << "Symbol ";
|
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
2016-10-23 03:46:27 +02:00
|
|
|
const char *
|
|
|
|
ircd::js::reflect(const JSContextOp &op)
|
|
|
|
{
|
|
|
|
switch(op)
|
|
|
|
{
|
|
|
|
case JSCONTEXT_NEW: return "JSCONTEXT_NEW";
|
|
|
|
case JSCONTEXT_DESTROY: return "JSCONTEXT_DESTROY";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2016-10-15 03:00:12 +02:00
|
|
|
const char *
|
|
|
|
ircd::js::reflect(const JSFinalizeStatus &s)
|
|
|
|
{
|
|
|
|
switch(s)
|
|
|
|
{
|
|
|
|
case JSFINALIZE_GROUP_START: return "GROUP_START";
|
|
|
|
case JSFINALIZE_GROUP_END: return "GROUP_END";
|
|
|
|
case JSFINALIZE_COLLECTION_END: return "COLLECTION_END";
|
|
|
|
}
|
|
|
|
|
2016-10-22 09:34:12 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
ircd::js::reflect(const JSGCParamKey &s)
|
|
|
|
{
|
|
|
|
switch(s)
|
|
|
|
{
|
|
|
|
case JSGC_MAX_BYTES: return "JSGC_MAX_BYTES";
|
|
|
|
case JSGC_MAX_MALLOC_BYTES: return "JSGC_MAX_MALLOC_BYTES";
|
|
|
|
case JSGC_BYTES: return "JSGC_BYTES";
|
|
|
|
case JSGC_NUMBER: return "JSGC_NUMBER";
|
|
|
|
case JSGC_MAX_CODE_CACHE_BYTES: return "JSGC_MAX_CODE_CACHE_BYTES";
|
|
|
|
case JSGC_MODE: return "JSGC_MODE";
|
|
|
|
case JSGC_UNUSED_CHUNKS: return "JSGC_UNUSED_CHUNKS";
|
|
|
|
case JSGC_TOTAL_CHUNKS: return "JSGC_TOTAL_CHUNKS";
|
|
|
|
case JSGC_SLICE_TIME_BUDGET: return "JSGC_SLICE_TIME_BUDGET";
|
|
|
|
case JSGC_MARK_STACK_LIMIT: return "JSGC_MARK_STACK_LIMIT";
|
|
|
|
case JSGC_HIGH_FREQUENCY_TIME_LIMIT: return "JSGC_HIGH_FREQUENCY_TIME_LIMIT";
|
|
|
|
case JSGC_HIGH_FREQUENCY_LOW_LIMIT: return "JSGC_HIGH_FREQUENCY_LOW_LIMIT";
|
|
|
|
case JSGC_HIGH_FREQUENCY_HIGH_LIMIT: return "JSGC_HIGH_FREQUENCY_HIGH_LIMIT";
|
|
|
|
case JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX: return "JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX";
|
|
|
|
case JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN: return "JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN";
|
|
|
|
case JSGC_LOW_FREQUENCY_HEAP_GROWTH: return "JSGC_LOW_FREQUENCY_HEAP_GROWTH";
|
|
|
|
case JSGC_DYNAMIC_HEAP_GROWTH: return "JSGC_DYNAMIC_HEAP_GROWTH";
|
|
|
|
case JSGC_DYNAMIC_MARK_SLICE: return "JSGC_DYNAMIC_MARK_SLICE";
|
|
|
|
case JSGC_ALLOCATION_THRESHOLD: return "JSGC_ALLOCATION_THRESHOLD";
|
|
|
|
case JSGC_DECOMMIT_THRESHOLD: return "JSGC_DECOMMIT_THRESHOLD";
|
|
|
|
case JSGC_MIN_EMPTY_CHUNK_COUNT: return "JSGC_MIN_EMPTY_CHUNK_COUNT";
|
|
|
|
case JSGC_MAX_EMPTY_CHUNK_COUNT: return "JSGC_MAX_EMPTY_CHUNK_COUNT";
|
|
|
|
case JSGC_COMPACTING_ENABLED: return "JSGC_COMPACTING_ENABLED";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
2016-10-15 03:00:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
ircd::js::reflect(const JSGCStatus &s)
|
|
|
|
{
|
|
|
|
switch(s)
|
|
|
|
{
|
|
|
|
case JSGC_BEGIN: return "BEGIN";
|
|
|
|
case JSGC_END: return "END";
|
|
|
|
}
|
|
|
|
|
2016-10-22 09:34:12 +02:00
|
|
|
return "";
|
2016-10-15 03:00:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
ircd::js::reflect(const JSExnType &e)
|
|
|
|
{
|
|
|
|
switch(e)
|
|
|
|
{
|
|
|
|
case JSEXN_NONE: return "?NONE?";
|
|
|
|
case JSEXN_ERR: return "Error";
|
|
|
|
case JSEXN_INTERNALERR: return "InternalError";
|
|
|
|
case JSEXN_EVALERR: return "EvalError";
|
|
|
|
case JSEXN_RANGEERR: return "RangeError";
|
|
|
|
case JSEXN_REFERENCEERR: return "ReferenceError";
|
|
|
|
case JSEXN_SYNTAXERR: return "SyntaxError";
|
|
|
|
case JSEXN_TYPEERR: return "TypeError";
|
|
|
|
case JSEXN_URIERR: return "URIError";
|
|
|
|
case JSEXN_LIMIT: return "?LIMIT?";
|
|
|
|
}
|
|
|
|
|
2016-10-22 09:34:12 +02:00
|
|
|
return "";
|
2016-10-15 03:00:12 +02:00
|
|
|
}
|
|
|
|
|
2016-10-16 03:44:51 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/compartment.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::compartment::compartment(JSObject *const &obj)
|
|
|
|
:compartment(obj, *cx)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::compartment::compartment(JSObject *const &obj,
|
|
|
|
context &c)
|
|
|
|
:c{&c}
|
|
|
|
,prev{JS_EnterCompartment(c, obj)}
|
2016-10-23 03:46:27 +02:00
|
|
|
,ours{::js::GetContextCompartment(c)}
|
2016-10-16 03:44:51 +02:00
|
|
|
,cprev{static_cast<compartment *>(JS_GetCompartmentPrivate(ours))}
|
|
|
|
{
|
|
|
|
JS_SetCompartmentPrivate(ours, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::compartment::compartment(compartment &&other)
|
|
|
|
noexcept
|
|
|
|
:c{std::move(other.c)}
|
|
|
|
,prev{std::move(other.prev)}
|
|
|
|
,ours{std::move(other.ours)}
|
|
|
|
,cprev{std::move(other.cprev)}
|
|
|
|
{
|
|
|
|
JS_SetCompartmentPrivate(ours, this);
|
|
|
|
other.ours = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::compartment::~compartment()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
// branch not taken on std::move()
|
|
|
|
if(ours)
|
|
|
|
{
|
|
|
|
JS_SetCompartmentPrivate(ours, cprev);
|
|
|
|
JS_LeaveCompartment(*c, prev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::for_each_compartment_our(const compartment::closure_our &closure)
|
|
|
|
{
|
|
|
|
for_each_compartment([&closure]
|
|
|
|
(JSCompartment *const &c)
|
|
|
|
{
|
|
|
|
if(our(c))
|
|
|
|
closure(*our(c));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::for_each_compartment(const compartment::closure &closure)
|
|
|
|
{
|
|
|
|
JS_IterateCompartments(*rt,
|
|
|
|
const_cast<compartment::closure *>(&closure),
|
|
|
|
compartment::handle_iterate);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::compartment::handle_iterate(JSRuntime *const rt,
|
|
|
|
void *const priv,
|
|
|
|
JSCompartment *const c)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-16 03:44:51 +02:00
|
|
|
{
|
|
|
|
const auto &closure(*static_cast<compartment::closure *>(priv));
|
|
|
|
closure(c);
|
|
|
|
}
|
|
|
|
|
2016-10-13 04:58:48 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/context.h
|
|
|
|
//
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
ircd::js::context::context(JSRuntime *const &runtime,
|
|
|
|
const struct opts &opts)
|
|
|
|
:custom_ptr<JSContext>
|
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
// Construct the context
|
|
|
|
[this, &runtime, &opts]
|
|
|
|
{
|
|
|
|
const auto ret(JS_NewContext(runtime, opts.stack_chunk_size));
|
|
|
|
|
|
|
|
// Use their privdata pointer to point to our instance. We can then use our(JSContext*)
|
|
|
|
// to get back to `this` instance.
|
|
|
|
JS_SetContextPrivate(ret, this);
|
|
|
|
|
|
|
|
// Assign the thread_local here.
|
|
|
|
cx = this;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}(),
|
|
|
|
|
|
|
|
// Plant the destruction
|
|
|
|
[](JSContext *const ctx)
|
2016-10-14 02:24:50 +02:00
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
if(!ctx)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Free the user's privdata managed object
|
|
|
|
delete static_cast<const privdata *>(JS_GetSecondContextPrivate(ctx));
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
JS_DestroyContext(ctx);
|
|
|
|
|
|
|
|
// Indicate to thread_local
|
|
|
|
cx = nullptr;
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
,opts{opts}
|
2016-10-22 05:32:45 +02:00
|
|
|
,except{nullptr}
|
2016-10-20 12:25:41 +02:00
|
|
|
,state
|
|
|
|
{{
|
|
|
|
0, // Semaphore value starting at 0.
|
|
|
|
phase::ACCEPT, // ACCEPT phase indicates nothing is running.
|
|
|
|
irq::JS, // irq::JS is otherwise here in case JS triggers an interrupt.
|
|
|
|
}}
|
2016-10-22 05:32:45 +02:00
|
|
|
,timer
|
|
|
|
{
|
|
|
|
std::bind(&context::handle_timeout, this)
|
|
|
|
}
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
assert(&our(runtime) == rt); // Trying to construct on thread without runtime thread_local
|
2016-10-22 05:32:45 +02:00
|
|
|
timer.set(opts.timer_limit);
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
ircd::js::context::~context()
|
2016-10-14 02:24:50 +02:00
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
void
|
2016-10-22 05:32:45 +02:00
|
|
|
ircd::js::context::handle_timeout()
|
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-23 08:36:18 +02:00
|
|
|
// At this time there is no yield logic so if the timer calls the script is terminated.
|
|
|
|
interrupt(*this, irq::TERMINATE);
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
bool
|
|
|
|
ircd::js::context::handle_interrupt()
|
2016-10-16 01:53:44 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
auto state(this->state.load(std::memory_order_acquire));
|
|
|
|
|
|
|
|
// Spurious interrupt; ignore.
|
|
|
|
if(unlikely(state.phase != phase::INTR && state.phase != phase::ENTER))
|
|
|
|
{
|
|
|
|
log.warning("context(%p): Spurious interrupt (irq: %02x)",
|
|
|
|
(const void *)this,
|
|
|
|
uint(state.irq));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// After the interrupt is handled the phase indicates entry back to JS,
|
|
|
|
// IRQ is left indicating JS in case we don't trigger the next interrupt.
|
|
|
|
const scope interrupt_return([this, &state]
|
|
|
|
{
|
|
|
|
state.phase = phase::ENTER;
|
|
|
|
state.irq = irq::JS;
|
|
|
|
this->state.store(state, std::memory_order_release);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Call the user hook if available
|
|
|
|
if(on_intr)
|
|
|
|
{
|
|
|
|
// The user's handler returns -1 for non-overriding behavior
|
|
|
|
const auto ret(on_intr(state.irq));
|
|
|
|
if(ret != -1)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(state.irq)
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
case irq::NONE:
|
|
|
|
assert(0);
|
|
|
|
|
|
|
|
case irq::JS:
|
|
|
|
case irq::USER:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case irq::YIELD:
|
|
|
|
ctx::yield();
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case irq::TERMINATE:
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-16 01:53:44 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
void
|
|
|
|
ircd::js::leave(context &c)
|
2016-10-20 04:50:55 +02:00
|
|
|
{
|
2016-10-22 05:32:45 +02:00
|
|
|
c.timer.cancel();
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
// Load the state to keep the current sem value up to date.
|
|
|
|
// This thread is the only writer to that value.
|
|
|
|
auto state(c.state.load(std::memory_order_relaxed));
|
|
|
|
|
|
|
|
// The ACCEPT phase locks out the interruptor
|
|
|
|
state.phase = phase::ACCEPT;
|
2016-10-20 04:50:55 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
// The ACCEPT is released and the current phase seen by interruptor is acquired.
|
|
|
|
state = c.state.exchange(state, std::memory_order_acq_rel);
|
|
|
|
|
|
|
|
// The executor (us) must check if the interruptor (them) has committed to an interrupt
|
|
|
|
// targeting the JS run we are now leaving. JS may have exited after the commitment
|
|
|
|
// and before the interrupt arrival.
|
|
|
|
if(state.phase == phase::INTR)
|
|
|
|
assert(interrupt_poll(c));
|
2016-10-20 04:50:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-10-20 12:25:41 +02:00
|
|
|
ircd::js::enter(context &c)
|
2016-10-20 04:50:55 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
// State was already acquired by the last leave();
|
|
|
|
auto state(c.state.load(std::memory_order_relaxed));
|
|
|
|
|
|
|
|
// Increment the semaphore for the next execution;
|
|
|
|
++state.sem;
|
|
|
|
|
|
|
|
// Set the IRQ to JS in case we don't trigger an interrupt, the handler
|
|
|
|
// will see a correct value.
|
|
|
|
state.irq = irq::JS;
|
|
|
|
state.phase = phase::ENTER;
|
|
|
|
|
|
|
|
// Commit to next execution.
|
|
|
|
c.state.store(state, std::memory_order_release);
|
2016-10-22 05:32:45 +02:00
|
|
|
c.timer.start();
|
2016-10-20 12:25:41 +02:00
|
|
|
}
|
2016-10-20 04:50:55 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
bool
|
|
|
|
ircd::js::interrupt(context &c,
|
2016-10-23 08:36:18 +02:00
|
|
|
const irq &req)
|
2016-10-20 12:25:41 +02:00
|
|
|
{
|
2016-10-23 08:36:18 +02:00
|
|
|
if(req == irq::NONE)
|
2016-10-20 12:25:41 +02:00
|
|
|
return false;
|
|
|
|
|
2016-10-23 08:36:18 +02:00
|
|
|
// Acquire the execution state. Proceed if something was running.
|
|
|
|
const auto state(c.state.load(std::memory_order_acquire));
|
|
|
|
if(state.phase != phase::ENTER)
|
2016-10-20 12:25:41 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// The expected value of the state to transact.
|
|
|
|
struct context::state in
|
2016-10-20 04:50:55 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
state.sem, // Expect the sem value to match, else the execution has changed.
|
|
|
|
phase::ENTER, // Expect the execution to be occurring.
|
|
|
|
irq::JS, // JS is always expected here instead of NONE.
|
|
|
|
};
|
|
|
|
|
|
|
|
// The restatement after the transaction.
|
|
|
|
const struct context::state out
|
|
|
|
{
|
|
|
|
state.sem, // Keep the same sem value.
|
|
|
|
phase::INTR, // Indicate our commitment to interrupting.
|
|
|
|
req // Include the irq type.
|
|
|
|
};
|
|
|
|
|
|
|
|
// Attempt commitment to interrupt here.
|
|
|
|
static const auto order_fail(std::memory_order_relaxed);
|
|
|
|
static const auto order_success(std::memory_order_acq_rel);
|
|
|
|
if(!c.state.compare_exchange_strong(in, out, order_success, order_fail))
|
|
|
|
{
|
|
|
|
// To reliably read from `in` here, order_fail should not be relaxed.
|
|
|
|
return false;
|
2016-10-20 04:50:55 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
// Commitment now puts the burden on the executor to not allow this interrupt to bleed into
|
|
|
|
// the next execution, even if JS has already exited before its arrival.
|
|
|
|
interrupt(c.runtime());
|
|
|
|
return true;
|
|
|
|
}
|
2016-10-20 04:50:55 +02:00
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
bool
|
|
|
|
ircd::js::interrupt_poll(const context &c)
|
2016-10-16 01:53:44 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
return JS_CheckForInterrupt(c);
|
2016-10-16 01:53:44 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
bool
|
|
|
|
ircd::js::restore_exception(context &c)
|
2016-10-16 01:53:44 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
if(unlikely(!c.except))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
JS_RestoreExceptionState(c, c.except);
|
|
|
|
c.except = nullptr;
|
|
|
|
return true;
|
2016-10-16 01:53:44 +02:00
|
|
|
}
|
|
|
|
|
2016-10-20 12:25:41 +02:00
|
|
|
void
|
|
|
|
ircd::js::save_exception(context &c,
|
|
|
|
const JSErrorReport &report)
|
2016-10-16 01:53:44 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
if(c.except)
|
|
|
|
throw error("(internal error): Won't overwrite saved exception");
|
|
|
|
|
|
|
|
c.except = JS_SaveExceptionState(c),
|
|
|
|
c.report = report;
|
2016-10-16 01:53:44 +02:00
|
|
|
}
|
|
|
|
|
2016-10-22 09:34:12 +02:00
|
|
|
void
|
|
|
|
ircd::js::run_gc(context &c)
|
|
|
|
{
|
|
|
|
JS_MaybeGC(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::out_of_memory(context &c)
|
|
|
|
{
|
|
|
|
JS_ReportOutOfMemory(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::allocation_overflow(context &c)
|
|
|
|
{
|
|
|
|
JS_ReportAllocationOverflow(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
ircd::js::get(context &c,
|
|
|
|
const JSGCParamKey &key)
|
|
|
|
{
|
|
|
|
//return JS_GetGCParameterForThread(c, key); // broken
|
|
|
|
return JS_GetGCParameter(c.runtime(), key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::set(context &c,
|
|
|
|
const JSGCParamKey &key,
|
|
|
|
const uint32_t &val)
|
|
|
|
{
|
|
|
|
//JS_SetGCParameterForThread(c, key, val); // broken
|
|
|
|
JS_SetGCParameter(c.runtime(), key, val);
|
|
|
|
}
|
|
|
|
|
2016-10-22 05:32:45 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/timer.h
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::js::timer::timer(const callback &timeout)
|
|
|
|
:finished{false}
|
|
|
|
,timeout{timeout}
|
|
|
|
,started{time_point::min()}
|
|
|
|
,limit{0us}
|
|
|
|
,state
|
|
|
|
{{
|
|
|
|
0, false
|
|
|
|
}}
|
|
|
|
,thread{std::bind(&timer::worker, this)}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::timer::~timer()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
// This is all on the main IRCd thread, and the only point when it waits on the timer
|
|
|
|
// thread. That's okay as long as this is on IRCd shutdown etc.
|
|
|
|
std::unique_lock<decltype(mutex)> lock(mutex);
|
|
|
|
finished = true;
|
|
|
|
|
|
|
|
// Wait for timer thread to exit. Notify before the unlock() could double tap,
|
|
|
|
// but should always make for a reliable way to do this shutdown.
|
|
|
|
cond.notify_all();
|
|
|
|
lock.unlock();
|
|
|
|
thread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::timer::time_point
|
|
|
|
ircd::js::timer::start()
|
|
|
|
{
|
|
|
|
// The counter is incremented indicating a new timing request, invalidating
|
|
|
|
// anything the timer was previously doing.
|
|
|
|
auto state(this->state.load(std::memory_order_relaxed));
|
|
|
|
++state.sem;
|
|
|
|
state.running = true;
|
|
|
|
|
|
|
|
// Commit to starting a new timer operation, unconditionally overwrite previous.
|
|
|
|
started = steady_clock::now();
|
|
|
|
this->state.store(state, std::memory_order_release);
|
|
|
|
|
|
|
|
// The timing thread is notified here. It may have already started on our new
|
|
|
|
// request. However, this notifcation must not be delayed and the timing thread
|
|
|
|
// must wake up soon/now.
|
|
|
|
cond.notify_one();
|
|
|
|
|
|
|
|
return started;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::timer::cancel()
|
|
|
|
{
|
|
|
|
// Relaxed acquire of the state to get the sem value.
|
|
|
|
auto state(this->state.load(std::memory_order_relaxed));
|
|
|
|
|
|
|
|
// Expect the timer to be running and order the timer to idle with an invalidation.
|
|
|
|
struct state in { state.sem, true };
|
|
|
|
const struct state out { state.sem + 1, false };
|
|
|
|
|
|
|
|
// Commit to cancellation. On a successful state transition, wake up the thread.
|
|
|
|
static const auto order_fail(std::memory_order_relaxed);
|
|
|
|
static const auto order_success(std::memory_order_acq_rel);
|
|
|
|
if(this->state.compare_exchange_strong(in, out, order_success, order_fail))
|
|
|
|
{
|
|
|
|
cond.notify_one();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::timer::set(const callback &timeout)
|
|
|
|
{
|
|
|
|
this->timeout = timeout;
|
|
|
|
std::atomic_thread_fence(std::memory_order_release);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::timer::set(const microseconds &limit)
|
|
|
|
{
|
|
|
|
this->limit.store(limit, std::memory_order_release);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::timer::worker()
|
|
|
|
{
|
|
|
|
// This lock is only ever held by this thread except during a finish condition.
|
|
|
|
// Notifications to the condition are only broadcast by the main thread.
|
|
|
|
std::unique_lock<decltype(mutex)> lock(mutex);
|
|
|
|
while(likely(!finished))
|
|
|
|
handle(lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::timer::handle(std::unique_lock<std::mutex> &lock)
|
|
|
|
{
|
|
|
|
struct state ours, theirs;
|
|
|
|
const auto idle_condition([this, &ours]
|
|
|
|
{
|
|
|
|
if(unlikely(finished))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Acquire the request operation
|
|
|
|
ours = this->state.load(std::memory_order_acquire);
|
|
|
|
|
|
|
|
// Require a start time
|
|
|
|
if(started == time_point::min())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return ours.running;
|
|
|
|
});
|
|
|
|
|
|
|
|
const auto cancel_condition([this, &ours, &theirs]
|
|
|
|
{
|
|
|
|
if(unlikely(finished))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Acquire the request state and compare it to our saved state to see
|
|
|
|
// if the counter has changed or if there is cancellation.
|
|
|
|
theirs = this->state.load(std::memory_order_consume);
|
|
|
|
return ours.sem != theirs.sem || !theirs.running;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Wait for a running condition into `in`
|
|
|
|
cond.wait(lock, idle_condition);
|
|
|
|
if(unlikely(finished))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Wait for timeout or cancellation
|
|
|
|
const auto limit(this->limit.load(std::memory_order_consume));
|
|
|
|
if(cond.wait_until(lock, started + limit, cancel_condition))
|
|
|
|
{
|
|
|
|
// A cancel or increment of the counter into the next request has occurred.
|
|
|
|
// This thread will go back to sleep in the idle_condition unless or until
|
|
|
|
// the next start() is triggered.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A timeout has occurred. This is the last chance for a belated cancellation to be
|
|
|
|
// observed. If a transition from running to !running can take place on this counter
|
|
|
|
// value, the user has not invalidated this request and desires the timeout.
|
|
|
|
assert(ours.running);
|
|
|
|
const struct state out { ours.sem, false };
|
|
|
|
static const auto order_fail(std::memory_order_relaxed);
|
|
|
|
static const auto order_success(std::memory_order_acq_rel);
|
|
|
|
if(state.compare_exchange_strong(ours, out, order_success, order_fail))
|
|
|
|
if(likely(timeout))
|
|
|
|
timeout();
|
|
|
|
}
|
|
|
|
|
2016-10-13 04:58:48 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// ircd/js/runtime.h
|
|
|
|
//
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
ircd::js::runtime::runtime(const struct opts &opts)
|
|
|
|
:custom_ptr<JSRuntime>
|
|
|
|
{
|
|
|
|
JS_NewRuntime(opts.maxbytes),
|
|
|
|
JS_DestroyRuntime
|
|
|
|
}
|
2016-10-17 21:16:30 +02:00
|
|
|
,opts{opts}
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-16 01:53:44 +02:00
|
|
|
// We use their privdata to find `this` via our(JSRuntime*) function.
|
|
|
|
// Any additional user privdata will have to ride a member in this class itself.
|
2016-10-14 02:24:50 +02:00
|
|
|
JS_SetRuntimePrivate(get(), this);
|
|
|
|
|
2016-10-16 01:53:44 +02:00
|
|
|
// Assign the thread_local runtime pointer here.
|
|
|
|
rt = this;
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
JS_SetErrorReporter(get(), handle_error);
|
|
|
|
JS::SetOutOfMemoryCallback(get(), handle_out_of_memory, nullptr);
|
|
|
|
JS::SetLargeAllocationFailureCallback(get(), handle_large_allocation_failure, nullptr);
|
|
|
|
JS_SetGCCallback(get(), handle_gc, nullptr);
|
|
|
|
JS_AddFinalizeCallback(get(), handle_finalize, nullptr);
|
2016-10-16 03:44:51 +02:00
|
|
|
JS_SetCompartmentNameCallback(get(), handle_compartment_name);
|
|
|
|
JS_SetDestroyCompartmentCallback(get(), handle_compartment_destroy);
|
2016-10-14 02:24:50 +02:00
|
|
|
JS_SetContextCallback(get(), handle_context, nullptr);
|
2016-10-20 12:25:41 +02:00
|
|
|
JS_SetInterruptCallback(get(), handle_interrupt);
|
2016-10-14 02:24:50 +02:00
|
|
|
|
|
|
|
JS_SetNativeStackQuota(get(), opts.code_stack_max, opts.trusted_stack_max, opts.untrusted_stack_max);
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::runtime::runtime(runtime &&other)
|
|
|
|
noexcept
|
|
|
|
:custom_ptr<JSRuntime>{std::move(other)}
|
2016-10-17 21:16:30 +02:00
|
|
|
,opts{std::move(other.opts)}
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
|
|
|
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
|
|
|
|
if(!!*this)
|
|
|
|
JS_SetRuntimePrivate(get(), this);
|
2016-10-16 01:53:44 +02:00
|
|
|
|
|
|
|
// Ensure the thread_local runtime always points here
|
|
|
|
rt = this;
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ircd::js::runtime &
|
|
|
|
ircd::js::runtime::operator=(runtime &&other)
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
static_cast<custom_ptr<JSRuntime> &>(*this) = std::move(other);
|
|
|
|
opts = std::move(other.opts);
|
|
|
|
|
|
|
|
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
|
|
|
|
if(!!*this)
|
|
|
|
JS_SetRuntimePrivate(get(), this);
|
|
|
|
|
2016-10-16 01:53:44 +02:00
|
|
|
// Ensure the thread_local runtime always points here
|
|
|
|
rt = this;
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2016-10-16 01:53:44 +02:00
|
|
|
ircd::js::runtime::~runtime()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
// Branch not taken on std::move()
|
|
|
|
if(!!*this)
|
|
|
|
rt = nullptr;
|
|
|
|
}
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
bool
|
|
|
|
ircd::js::runtime::handle_interrupt(JSContext *const ctx)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-20 12:25:41 +02:00
|
|
|
log.debug("JSContext(%p): Interrupt", (const void *)ctx);
|
|
|
|
|
|
|
|
auto &c(our(ctx));
|
|
|
|
return c.handle_interrupt();
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ircd::js::runtime::handle_context(JSContext *const c,
|
|
|
|
const uint op,
|
|
|
|
void *const priv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-23 03:46:27 +02:00
|
|
|
log.debug("context(%p): %s (priv: %p)",
|
|
|
|
(const void *)c,
|
|
|
|
reflect(static_cast<JSContextOp>(op)),
|
|
|
|
priv);
|
|
|
|
|
2016-10-14 02:24:50 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-10-16 03:44:51 +02:00
|
|
|
ircd::js::runtime::handle_compartment_destroy(JSFreeOp *const fop,
|
|
|
|
JSCompartment *const compartment)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-23 03:46:27 +02:00
|
|
|
log.debug("runtime(%p): compartment(%p) destroy: fop(%p)",
|
|
|
|
(const void *)(our_runtime(*fop).ptr()),
|
|
|
|
(const void *)compartment,
|
|
|
|
(const void *)fop);
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-10-16 03:44:51 +02:00
|
|
|
ircd::js::runtime::handle_compartment_name(JSRuntime *const rt,
|
|
|
|
JSCompartment *const compartment,
|
|
|
|
char *const buf,
|
|
|
|
const size_t max)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-23 03:46:27 +02:00
|
|
|
log.debug("runtime(%p): comaprtment: %p (buf@%p: max: %zu)",
|
|
|
|
(const void *)rt,
|
|
|
|
(const void *)compartment,
|
|
|
|
(const void *)buf,
|
|
|
|
max);
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::runtime::handle_finalize(JSFreeOp *const fop,
|
|
|
|
const JSFinalizeStatus status,
|
|
|
|
const bool is_compartment,
|
|
|
|
void *const priv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-15 03:00:12 +02:00
|
|
|
log.debug("fop(%p): %s %s",
|
|
|
|
(const void *)fop,
|
|
|
|
reflect(status),
|
|
|
|
is_compartment? "COMPARTMENT" : "");
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::runtime::handle_gc(JSRuntime *const rt,
|
|
|
|
const JSGCStatus status,
|
|
|
|
void *const priv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
2016-10-15 03:00:12 +02:00
|
|
|
log.debug("runtime(%p): GC %s",
|
|
|
|
(const void *)rt,
|
|
|
|
reflect(status));
|
2016-10-14 02:24:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::runtime::handle_large_allocation_failure(void *const priv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
|
|
|
log.error("Large allocation failure");
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::js::runtime::handle_out_of_memory(JSContext *const ctx,
|
|
|
|
void *const priv)
|
2016-10-18 03:43:23 +02:00
|
|
|
noexcept
|
2016-10-14 02:24:50 +02:00
|
|
|
{
|
|
|
|
log.error("JSContext(%p): out of memory", (const void *)ctx);
|
|
|
|
}
|
|
|
|
|
2016-10-13 04:58:48 +02:00
|
|
|
void
|
|
|
|
ircd::js::runtime::handle_error(JSContext *const ctx,
|
|
|
|
const char *const msg,
|
|
|
|
JSErrorReport *const report)
|
2016-10-17 21:16:30 +02:00
|
|
|
noexcept
|
2016-10-13 04:58:48 +02:00
|
|
|
{
|
2016-10-17 21:16:30 +02:00
|
|
|
assert(report);
|
2016-10-20 04:50:55 +02:00
|
|
|
/*
|
|
|
|
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());
|
|
|
|
*/
|
2016-10-20 12:25:41 +02:00
|
|
|
save_exception(our(ctx), *report);
|
2016-10-13 04:58:48 +02:00
|
|
|
}
|