diff --git a/nasal-web-app/server.js b/nasal-web-app/server.js index d0784c2..8acbbaf 100644 --- a/nasal-web-app/server.js +++ b/nasal-web-app/server.js @@ -3,6 +3,7 @@ const path = require('path'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const koffi = require('koffi'); +require('expose-gc'); // Parse command line arguments const argv = yargs(hideBin(process.argv)) @@ -37,7 +38,7 @@ let nasalLib; try { // First load the library const lib = koffi.load(path.join(__dirname, '../module/libnasal-web.dylib')); - + // Then declare the functions explicitly nasalLib = { nasal_init: lib.func('nasal_init', 'void*', []), @@ -45,7 +46,7 @@ try { nasal_eval: lib.func('nasal_eval', 'const char*', ['void*', 'const char*', 'int']), nasal_get_error: lib.func('nasal_get_error', 'const char*', ['void*']) }; - + } catch (err) { console.error('Failed to load nasal library:', err); process.exit(1); @@ -66,7 +67,7 @@ app.post('/eval', (req, res) => { try { const result = nasalLib.nasal_eval(ctx, code, showTime ? 1 : 0); const error = nasalLib.nasal_get_error(ctx); - + if (error && error !== 'null') { if (argv.verbose) console.log('Nasal error:', error); res.json({ error: error }); @@ -83,6 +84,7 @@ app.post('/eval', (req, res) => { } finally { if (argv.verbose) console.log('Cleaning up Nasal context'); nasalLib.nasal_cleanup(ctx); + global.gc() } }); @@ -91,4 +93,4 @@ app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); console.log(`Visit http://localhost:${PORT} to use the Nasal interpreter`); if (argv.verbose) console.log('Verbose logging enabled'); -}); \ No newline at end of file +}); diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index c04c7ae..1eee155 100644 --- a/src/nasal_vm.cpp +++ b/src/nasal_vm.cpp @@ -191,7 +191,7 @@ void vm::value_info(var& val) { void vm::function_detail_info(const nas_func& func) { std::clog << "func@"; std::clog << std::hex << reinterpret_cast(&func) << std::dec; - + std::vector argument_list = {}; argument_list.resize(func.keys.size()); for(const auto& key : func.keys) { @@ -273,7 +273,7 @@ void vm::function_call_trace() { std::clog << " `--> " << same_count << " same call(s)\n"; same_count = 0; } - + last = func; last_callsite = place; @@ -376,7 +376,7 @@ void vm::global_state() { if (name.length()>=10) { name = name.substr(0, 7) + "..."; } else { - + } std::clog << "| " << std::left << std::setw(10) << std::setfill(' ') << name << " "; @@ -570,6 +570,14 @@ void vm::run(const codegen& gen, #ifndef _MSC_VER // using labels as values/computed goto + + // Define an interrupt check macro for computed goto mode. + #define CHECK_INTERRUPT { \ + if (interrupt_ptr && interrupt_ptr->load()) { \ + throw std::runtime_error("VM execution interrupted by timeout"); \ + } \ + } + const void* oprs[] = { &&vmexit, &&repl, @@ -665,6 +673,7 @@ void vm::run(const codegen& gen, code.push_back(oprs[i.op]); imm.push_back(i.num); } + CHECK_INTERRUPT; // goto the first operand goto *code[ctx.pc]; #else @@ -674,6 +683,9 @@ void vm::run(const codegen& gen, imm.push_back(i.num); } while(code[ctx.pc]) { + if (interrupt_ptr && interrupt_ptr->load()) { + throw std::runtime_error("VM execution interrupted by timeout"); + } (this->*code[ctx.pc])(); if (ctx.top>=ctx.canary) { die("stack overflow"); @@ -704,6 +716,7 @@ vmexit: // do not cause stackoverflow #define exec_nodie(op) {\ op();\ + CHECK_INTERRUPT;\ goto *code[++ctx.pc];\ } diff --git a/src/nasal_vm.h b/src/nasal_vm.h index 312d031..43e6644 100644 --- a/src/nasal_vm.h +++ b/src/nasal_vm.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "nasal_import.h" #include "nasal_gc.h" @@ -31,7 +32,7 @@ protected: /* nasal native functions */ std::vector native_function; - + /* garbage collector */ gc ngc; @@ -323,6 +324,13 @@ public: auto get_gc_time_ms() const { return ngc.get_gc_time_ms(); } + + void set_interrupt_ptr(std::atomic* p) { + interrupt_ptr = p; + } + +private: + std::atomic* interrupt_ptr = nullptr; }; inline bool vm::boolify(const var& val) { @@ -977,7 +985,7 @@ inline void vm::o_callfh() { for(u32 i = 0; i #include #include +#include namespace { // Helper function implementations inside anonymous namespace @@ -46,9 +47,15 @@ struct NasalContext { std::string last_result; std::string last_error; std::chrono::seconds timeout{5}; // Default 5 second timeout + std::atomic interrupted{false}; NasalContext() { vm_instance = std::make_unique(); + vm_instance->set_interrupt_ptr(&interrupted); + } + + ~NasalContext() { + vm_instance.reset(); // Reset explicitly } }; @@ -71,7 +78,9 @@ void* nasal_init() { } void nasal_cleanup(void* context) { - delete static_cast(context); + auto* ctx = static_cast(context); + ctx->vm_instance.reset(); + delete ctx; } // Add new function to set timeout @@ -148,6 +157,7 @@ const char* nasal_eval(void* context, const char* code, int show_time) { // Wait for completion or timeout auto status = future.wait_for(ctx->timeout); if (status == std::future_status::timeout) { + ctx->interrupted.store(true); std::remove(temp_filename); throw std::runtime_error("Execution timed out after " + std::to_string(ctx->timeout.count()) + " seconds");