Skip to content

File machine.cpp

File List > jac > machine > machine.cpp

Go to the documentation of this file

#include "machine.h"

#include <cstring>


namespace jac {


static int checkModuleAttributes(JSContext* ctx, void* /*opaque*/, JSValueConst attributes) {
    if (JS_IsUndefined(attributes)) {
        return 0;
    }

    JSPropertyEnum* tab = nullptr;
    uint32_t len = 0;
    if (JS_GetOwnPropertyNames(ctx, &tab, &len, attributes, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) {
        return -1;
    }

    int ret = 0;
    for (uint32_t i = 0; i < len; ++i) {
        size_t nameLen = 0;
        const char* name = JS_AtomToCStringLen(ctx, &nameLen, tab[i].atom);
        if (!name) {
            ret = -1;
            break;
        }
        if (!(nameLen == 4 && std::memcmp(name, "type", nameLen) == 0)) {
            JS_ThrowTypeError(ctx, "import attribute '%s' is not supported", name);
            ret = -1;
        }
        JS_FreeCString(ctx, name);
        if (ret) {
            break;
        }
    }

    JS_FreePropertyEnum(ctx, tab, len);
    return ret;
}


Module::Module(ContextRef ctx, std::string name) : _ctx(ctx), _exports(Object::create(ctx)) {
    _def = JS_NewCModule(ctx, name.c_str(), [](JSContext* context, JSModuleDef* def) noexcept -> int {
        JSValue carrier = JS_GetModulePrivateValue(context, def);

        int ret = 0;
        try {
            // if no error has occurred, should contain an object
            if (JS_IsObject(carrier)) {
                Object exports(context, carrier);  // takes ownership of the ref
                for (Atom& key : exports.getOwnPropertyNames()) {
                    Value val = exports.get<Value>(key);
                    if (JS_SetModuleExport(context, def, key.toString().c_str(), val.loot().second) != 0) {
                        ret = -1;
                    }
                }
            } else {
                JS_FreeValue(context, carrier);
            }
        } catch (...) {
            ret = -1;
        }

        JS_SetModulePrivateValue(context, def, JS_UNDEFINED);
        return ret;
    });
    if (!_def) {
        throw std::runtime_error("JS_NewCModule failed");
    }
}

void Module::addExport(std::string name, Value val) {
    if (JS_AddModuleExport(_ctx, _def, name.c_str()) != 0) {
        throw Exception::create(Exception::Type::Error, "failed to add module export '" + name + "'");
    }
    _exports.set(name, val);
}

void Module::finalize() {
    JS_SetModulePrivateValue(_ctx, _def, _exports.loot().second);
}

void MachineBase::initialize() {
    // last in stack

    if (mallocFunctions) {
        _runtime = JS_NewRuntime2(mallocFunctions, nullptr);
    }
    else {
        _runtime = JS_NewRuntime();
    }
    _context = JS_NewContext(_runtime);

    JS_SetContextOpaque(_context, this);
    JS_SetInterruptHandler(_runtime, [](JSRuntime*, void* opaque) noexcept {
        MachineBase& base = *static_cast<MachineBase*>(opaque);
        if (base._interrupt) {
            base._interrupt = false;
            return 1;
        }
        if (base._watchdogTimeout.count() > 0) {
            auto now = std::chrono::steady_clock::now();
            if (now > base._watchdogNext) {
                base._watchdogNext = now + base._watchdogTimeout;
                if (!base._wathdogCallback || base._wathdogCallback()) {
                    return 1;
                }
            }
        }
        return 0;
    }, this);

    JS_SetModuleLoaderFunc2(_runtime, nullptr, loadModule, checkModuleAttributes, this);
}

Value MachineBase::eval(std::string code, std::string filename, EvalFlags flags /*= EvalFlags::Global*/) {
    resetWatchdog();
    Value bytecode(_context, JS_Eval(_context, code.c_str(), code.size(), filename.c_str(), static_cast<int>(flags | EvalFlags::CompileOnly)));
    if (static_cast<int>(flags & EvalFlags::CompileOnly) != 0) {
        return bytecode;
    }
    code = "";
    resetWatchdog();
    return Value(_context, JS_EvalFunction(_context, bytecode.loot().second));
}

void MachineBase::newModule(std::string name, ModuleBuilder builder) {
    auto [it, inserted] = _moduleBuilders.emplace(std::move(name), std::move(builder));
    if (!inserted) {
        throw std::runtime_error("module already defined: " + it->first);
    }
}

JSModuleDef* MachineBase::loadModule(JSContext* ctx, const char* name, void* opaque, JSValueConst attributes) {
    auto& self = *static_cast<MachineBase*>(opaque);

    auto it = self._moduleBuilders.find(name);
    if (it != self._moduleBuilders.end()) {
        JSModuleDef* def = nullptr;
        try {
            Module mdl(self._context, name);
            def = mdl.get();
            it->second(mdl);
            mdl.finalize();
            return mdl.get();
        }
        catch (...) {
            if (def) {
                JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, def));
            }
            try {
                throw;
            }
            catch (Exception& e) {
                e.throwJS(ctx);
            }
            catch (std::exception& e) {
                Exception::create(Exception::Type::InternalError, e.what()).throwJS(ctx);
            }
            catch (...) {
                Exception::create(Exception::Type::InternalError, "unknown error").throwJS(ctx);
            }
            return nullptr;
        }
    }

    if (self._fileModuleLoader) {
        return self._fileModuleLoader(ctx, name, attributes);
    }

    JS_ThrowReferenceError(ctx, "could not load module '%s'", name);
    return nullptr;
}


} // namespace jac