Something went wrong. Try again.
MIRROR: javascript for 🐜's, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469#include <compat.h> // IWYU pragma: keep
#include "modules/sandbox.h"#include "modules/json.h"#include "modules/timer.h"
#include "ant.h"#include "cfunc.h"#include "errors.h"#include "gc/roots.h"#include <stdatomic.h>#include "list.h"#include "internal.h"#include "object.h"#include "ptr.h"#include "sandbox/host.h"#include "sandbox/sandbox.h"#include "sandbox/transport.h"#include "sandbox/vm.h"#include "silver/call.h"#include "modules/symbol.h"#include "modules/iterator.h"
#include <errno.h>#include <limits.h>#include <math.h>#include <stdbool.h>#include <stdint.h>#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <unistd.h>#include <uv.h>
#ifndef _WIN32#include <sys/socket.h>#endif
static constexpr uint32_t SANDBOX_NATIVE_TAG = Ant_NativeTag("SBOX");
struct sandbox_state { ant_sandbox_assets_t assets; ant_sandbox_launch_options_t launch; ant_sandbox_vm_session_t *session; uint32_t capabilities; uint16_t tty_rows; uint16_t tty_cols; unsigned int timeout_ms; unsigned int boot_timeout_ms; unsigned int cpu_time_ms; unsigned long long memory_size; ant_sandbox_vm_stats_t last_stats; bool verbose; bool closed; bool running; bool worker_started; bool run_done; bool close_requested; bool async_initialized; bool mutex_initialized; pthread_t worker; pthread_mutex_t mutex; uv_async_t async; ant_t *js; ant_value_t self; ant_value_t run_promise; ant_value_t close_promise; bool has_run_promise; bool has_close_promise; uint8_t *run_request; size_t run_request_len; int run_rc; ant_sandbox_vm_result_t run_result; struct sandbox_message_node *message_head; struct sandbox_message_node *message_tail; struct sandbox_message_waiter *waiter_head; struct sandbox_message_waiter *waiter_tail; sandbox_state_t *next; sandbox_state_t **pprev;};
typedef struct sandbox_message_node { char *data; size_t len; struct sandbox_message_node *next;} sandbox_message_node_t;
typedef struct sandbox_message_waiter { ant_value_t promise; char *type; bool iterator; struct sandbox_message_waiter *next;} sandbox_message_waiter_t;
static bool sandbox_parse_memory_size(const char *input, unsigned long long *out) { if (!input || !input[0] || !out) return false;
char *end = NULL; errno = 0; double value = strtod(input, &end); if (errno != 0 || end == input || !isfinite(value) || value <= 0) return false;
while (*end == ' ' || *end == '\t') end++; unsigned long long scale = 1; if (*end != '\0') { if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "kib") == 0) scale = 1024ull; else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "mib") == 0) scale = 1024ull * 1024ull; else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "gib") == 0) scale = 1024ull * 1024ull * 1024ull; else return false; }
long double bytes = (long double)value * (long double)scale; if (bytes < 1 || bytes > (long double)ULLONG_MAX) return false; *out = (unsigned long long)bytes; return true;}
typedef struct { ant_t *js; ant_value_t obj; uv_poll_t poll; bool poll_initialized; bool closing; unsigned char *input; size_t input_len; size_t input_cap;} sandbox_guest_port_t;
static sandbox_guest_port_t g_guest_port = {0};static _Atomic(ant_t *) g_guest_port_owner = NULL;
static uint32_t sandbox_load_u32(const unsigned char *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);}
static void sandbox_guest_port_close_cb(uv_handle_t *handle) { sandbox_guest_port_t *port = handle ? handle->data : NULL; if (!port) return; port->poll_initialized = false; free(port->input); port->input = NULL; port->input_len = 0; port->input_cap = 0;}
static void sandbox_guest_port_close(sandbox_guest_port_t *port) { if (!port || port->closing) return; port->closing = true; if (port->poll_initialized) { uv_poll_stop(&port->poll); if (!uv_is_closing((uv_handle_t *)&port->poll)) uv_close((uv_handle_t *)&port->poll, sandbox_guest_port_close_cb); }}
static void sandbox_guest_deliver_message(sandbox_guest_port_t *port, const void *payload, size_t payload_len) { if (!port || !port->js || !is_object_type(port->obj)) return; ant_t *js = port->js;
ant_value_t encoded = js_mkstr(js, payload, payload_len); ant_value_t message = json_parse_value(js, encoded);
if (is_err(message)) { js_take_thrown(js, message); return; }
ant_value_t handler = js_get(js, port->obj, "onmessage"); if (!is_callable(handler)) handler = js_get(js, port->obj, "_messageHandler"); if (!is_callable(handler)) return;
ant_value_t args[1] = { message }; sv_vm_call(js->vm, js, handler, port->obj, args, 1, NULL, js_mkundef()); js_maybe_drain_microtasks_after_async_settle(js);}
static bool sandbox_guest_append_input(sandbox_guest_port_t *port, const unsigned char *data, size_t len) { if (!port || !data || len == 0) return true; if (port->input_len > ANT_SANDBOX_FRAME_MAX_SIZE - len) return false; size_t needed = port->input_len + len; if (needed > port->input_cap) { size_t cap = port->input_cap ? port->input_cap * 2 : 4096; while (cap < needed) cap *= 2; unsigned char *next = realloc(port->input, cap); if (!next) return false; port->input = next; port->input_cap = cap; } memcpy(port->input + port->input_len, data, len); port->input_len += len; return true;}
static bool sandbox_guest_process_input(sandbox_guest_port_t *port) { size_t off = 0; while (port->input_len - off >= ANT_SANDBOX_FRAME_HEADER_SIZE) { const unsigned char *frame = port->input + off; if (memcmp(frame, ANT_SANDBOX_FRAME_MAGIC, 4) != 0 || frame[4] != ANT_SANDBOX_FRAME_VERSION) return false; uint32_t payload_len = sandbox_load_u32(frame + 8); if (payload_len > ANT_SANDBOX_FRAME_MAX_SIZE - ANT_SANDBOX_FRAME_HEADER_SIZE) return false; size_t frame_len = ANT_SANDBOX_FRAME_HEADER_SIZE + (size_t)payload_len; if (port->input_len - off < frame_len) break;
if (frame[5] == ANT_SANDBOX_FRAME_MESSAGE) sandbox_guest_deliver_message(port, frame + ANT_SANDBOX_FRAME_HEADER_SIZE, payload_len); else if (frame[5] == ANT_SANDBOX_FRAME_CLOSE) sandbox_guest_port_close(port); else return false; off += frame_len; } if (off > 0) { memmove(port->input, port->input + off, port->input_len - off); port->input_len -= off; } return true;}
static void sandbox_guest_poll_cb(uv_poll_t *handle, int status, int events) {#ifdef _WIN32 (void)handle; (void)status; (void)events;#else sandbox_guest_port_t *port = handle ? handle->data : NULL; if (!port || status < 0 || !(events & UV_READABLE)) { sandbox_guest_port_close(port); return; } unsigned char buf[8192]; for (;;) { ssize_t n = recv(ant_sandbox_transport_fd(), buf, sizeof(buf), MSG_DONTWAIT); if (n > 0) { if (!sandbox_guest_append_input(port, buf, (size_t)n) || !sandbox_guest_process_input(port)) { sandbox_guest_port_close(port); return; } continue; } if (n == 0) sandbox_guest_port_close(port); else if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) sandbox_guest_port_close(port); break; }#endif}
static ant_value_t sandbox_guest_send(ant_params_t) { if (!ant_sandbox_is_guest_process()) return js_mkerr_typed(js, JS_ERR_TYPE, "parentPort is only available inside a sandbox"); if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "parentPort.send(value) requires a value"); ant_value_t encoded = json_stringify_value(js, args[0]); if (is_err(encoded)) return encoded; size_t len = 0; const char *payload = js_getstr(js, encoded, &len); if (!payload || !ant_sandbox_transport_send_frame(ANT_SANDBOX_FRAME_MESSAGE, payload, len)) return js_mkerr(js, "failed to send sandbox message"); return js_mkundef();}
static ant_value_t sandbox_guest_on(ant_params_t) { if (nargs < 2 || vtype(args[0]) != kTypeString || !is_callable(args[1])) return js_mkerr_typed(js, JS_ERR_TYPE, "parentPort.on('message', handler) requires a handler"); const char *event = js_getstr(js, args[0], NULL); if (!event || strcmp(event, "message") != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "parentPort only supports the 'message' event"); js_set(js, js->this_val, "_messageHandler", args[1]); return js->this_val;}
static ant_value_t sandbox_guest_close(ant_params_t) { (void)js; (void)args; (void)nargs; sandbox_guest_port_close(&g_guest_port); return js_mkundef();}
static ant_value_t sandbox_guest_parent_port(ant_t *js) { if (!ant_sandbox_is_guest_process()) return js_mkundef(); ant_t *owner = NULL; if (!atomic_compare_exchange_strong(&g_guest_port_owner, &owner, js) && owner != js) return js_mkundef(); if (g_guest_port.js == js && is_object_type(g_guest_port.obj)) return g_guest_port.obj;
memset(&g_guest_port, 0, sizeof(g_guest_port)); g_guest_port.js = js; g_guest_port.obj = Ant_Object_CreateBare(js); js_set(js, g_guest_port.obj, "send", Ant_Function_CreateWithArity(sandbox_guest_send, 1)); js_set(js, g_guest_port.obj, "on", Ant_Function_CreateWithArity(sandbox_guest_on, 2)); js_set(js, g_guest_port.obj, "close", Ant_Function_Create(sandbox_guest_close)); js_set(js, g_guest_port.obj, "onmessage", js_mkundef());
#ifndef _WIN32 int fd = ant_sandbox_transport_fd(); if (fd >= 0 && uv_poll_init(js->loop, &g_guest_port.poll, fd) == 0) { g_guest_port.poll.data = &g_guest_port; g_guest_port.poll_initialized = true; if (uv_poll_start(&g_guest_port.poll, UV_READABLE, sandbox_guest_poll_cb) != 0) sandbox_guest_port_close(&g_guest_port); }#endif return g_guest_port.obj;}
static inline sandbox_state_t *sandbox_get_state(ant_value_t value) { return (sandbox_state_t *)js_get_native(value, SANDBOX_NATIVE_TAG);}
static void sandbox_add_active(sandbox_state_t *state) { if (state) Ant_List_Insert(&state->js->sandboxes, state);}
static void sandbox_remove_active(sandbox_state_t *state) { if (state) Ant_List_Remove(state);}
static void sandbox_free_messages(sandbox_state_t *state) { sandbox_message_node_t *node = state ? state->message_head : NULL; while (node) { sandbox_message_node_t *next = node->next; free(node->data); free(node); node = next; } if (state) state->message_head = state->message_tail = NULL;}
static void sandbox_free_waiters(sandbox_state_t *state) { sandbox_message_waiter_t *waiter = state ? state->waiter_head : NULL; while (waiter) { sandbox_message_waiter_t *next = waiter->next; free(waiter->type); free(waiter); waiter = next; } if (state) state->waiter_head = state->waiter_tail = NULL;}
static void sandbox_update_stats(sandbox_state_t *state) { if (!state || !state->session) return; ant_sandbox_vm_stats_t current; if (ant_sandbox_vm_session_stats(state->session, ¤t) == 0) state->last_stats = current;}
static void sandbox_state_destroy(sandbox_state_t *state) { if (!state) return; sandbox_remove_active(state); sandbox_update_stats(state); if (state->worker_started) { ant_sandbox_vm_session_cancel(state->session); pthread_join(state->worker, NULL); } if (state->async_initialized && !uv_is_closing((uv_handle_t *)&state->async)) uv_close((uv_handle_t *)&state->async, NULL); ant_sandbox_vm_session_destroy(state->session); state->session = NULL; ant_sandbox_launch_options_cleanup(&state->launch); sandbox_free_messages(state); sandbox_free_waiters(state); free(state->run_request); if (state->mutex_initialized) pthread_mutex_destroy(&state->mutex); free(state);}
static void sandbox_finalize(ant_t *js, ant_object_t *obj) { ant_value_t value = js_obj_from_ptr(obj); sandbox_state_t *state = sandbox_get_state(value); if (!state) return; js_clear_native(value, SANDBOX_NATIVE_TAG); sandbox_state_destroy(state);}
typedef int (*sandbox_string_cb_t)( ant_t *js, const char *value, void *udata);
static int sandbox_for_each_string( ant_t *js, ant_value_t value, const char *name, sandbox_string_cb_t cb, void *udata, ant_value_t *error_out) { if (vtype(value) == kTypeUndefined || vtype(value) == kTypeNull) return 0;
if (vtype(value) == kTypeString) { const char *str = js_getstr(js, value, NULL); if (!str) { *error_out = js_mkerr(js, "oom"); return -ENOMEM; } return cb(js, str, udata); }
if (js_is_array(value)) { ant_offset_t len = js_arr_len(js, value); for (ant_offset_t i = 0; i < len; i++) { ant_value_t item = js_arr_get(js, value, i); if (vtype(item) != kTypeString) { *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "%s entries must be strings", name); return -EINVAL; } const char *str = js_getstr(js, item, NULL); if (!str) { *error_out = js_mkerr(js, "oom"); return -ENOMEM; } int rc = cb(js, str, udata); if (rc != 0) return rc; } return 0; }
*error_out = js_mkerr_typed(js, JS_ERR_TYPE, "%s must be a string or array of strings", name); return -EINVAL;}
typedef struct { sandbox_state_t *state; bool readonly; char error[512];} sandbox_mount_parse_t;
typedef struct { sandbox_state_t *state; char error[512];} sandbox_forward_parse_t;
static int sandbox_add_mount_option(ant_t *js, const char *value, void *udata) { sandbox_mount_parse_t *ctx = udata; return ant_sandbox_launch_add_mount(&ctx->state->launch, value, ctx->readonly, ctx->error, sizeof(ctx->error));}
static int sandbox_add_forward_option(ant_t *js, const char *value, void *udata) { sandbox_forward_parse_t *ctx = udata; return ant_sandbox_launch_add_forward(&ctx->state->launch, value, ctx->error, sizeof(ctx->error));}
static ant_value_t sandbox_apply_string_list_option( ant_t *js, ant_value_t opts, const char *key, sandbox_string_cb_t cb, void *udata, const char *type_name) { ant_value_t value = js_get(js, opts, key); ant_value_t error = js_mkundef(); int rc = sandbox_for_each_string(js, value, type_name, cb, udata, &error); if (rc == 0) return js_mkundef(); if (vtype(error) != kTypeUndefined) return error; return js_mkerr_typed(js, JS_ERR_TYPE, "invalid %s option", key);}
static ant_value_t sandbox_apply_options(ant_t *js, sandbox_state_t *state, ant_value_t opts) { if (vtype(opts) == kTypeUndefined || vtype(opts) == kTypeNull) return js_mkundef();
if (vtype(opts) == kTypeString) { sandbox_mount_parse_t mount_ctx = { .state = state, .readonly = true }; const char *opts_str = js_getstr(js, opts, NULL); if (!opts_str) return js_mkerr(js, "out of memory"); int rc = sandbox_add_mount_option(js, opts_str, &mount_ctx); if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", mount_ctx.error[0] ? mount_ctx.error : "invalid mount option"); return js_mkundef(); }
if (!is_object_type(opts)) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox options must be an object");
sandbox_mount_parse_t mount_ctx = { .state = state, .readonly = true }; ant_value_t result = sandbox_apply_string_list_option(js, opts, "mount", sandbox_add_mount_option, &mount_ctx, "mount"); if (is_err(result)) return mount_ctx.error[0] ? js_mkerr_typed(js, JS_ERR_TYPE, "%s", mount_ctx.error) : result;
sandbox_mount_parse_t write_ctx = { .state = state, .readonly = false }; result = sandbox_apply_string_list_option(js, opts, "write", sandbox_add_mount_option, &write_ctx, "write"); if (is_err(result)) return write_ctx.error[0] ? js_mkerr_typed(js, JS_ERR_TYPE, "%s", write_ctx.error) : result;
sandbox_forward_parse_t forward_ctx = { .state = state }; result = sandbox_apply_string_list_option(js, opts, "forward", sandbox_add_forward_option, &forward_ctx, "forward"); if (is_err(result)) return forward_ctx.error[0] ? js_mkerr_typed(js, JS_ERR_TYPE, "%s", forward_ctx.error) : result;
ant_value_t cwd = js_get(js, opts, "cwd"); if (vtype(cwd) != kTypeUndefined && vtype(cwd) != kTypeNull) { if (vtype(cwd) != kTypeString) return js_mkerr_typed(js, JS_ERR_TYPE, "cwd must be a string"); const char *cwd_str = js_getstr(js, cwd, NULL); if (!cwd_str || cwd_str[0] != '/') return js_mkerr_typed(js, JS_ERR_TYPE, "cwd must be an absolute guest path"); int written = snprintf(state->launch.guest_cwd, sizeof(state->launch.guest_cwd), "%s", cwd_str); if (written < 0 || (size_t)written >= sizeof(state->launch.guest_cwd)) return js_mkerr_typed(js, JS_ERR_RANGE, "cwd is too long"); }
ant_value_t verbose = js_get(js, opts, "verbose"); if (vtype(verbose) != kTypeUndefined && vtype(verbose) != kTypeNull) state->verbose = js_truthy(js, verbose);
ant_value_t timeout = js_get(js, opts, "timeoutMs"); if (vtype(timeout) == kTypeUndefined) timeout = js_get(js, opts, "timeout"); if (vtype(timeout) != kTypeUndefined && vtype(timeout) != kTypeNull) { if (vtype(timeout) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "timeoutMs must be a number"); double n = js_getnum(timeout); if (n < 0 || n > UINT_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "timeoutMs is out of range"); state->timeout_ms = (unsigned int)n; }
ant_value_t boot_timeout = js_get(js, opts, "bootTimeoutMs"); if (vtype(boot_timeout) == kTypeUndefined) boot_timeout = js_get(js, opts, "bootTimeout"); if (vtype(boot_timeout) == kTypeUndefined) boot_timeout = js_get(js, opts, "requestTimeoutMs"); if (vtype(boot_timeout) == kTypeUndefined) boot_timeout = js_get(js, opts, "requestTimeout"); if (vtype(boot_timeout) != kTypeUndefined && vtype(boot_timeout) != kTypeNull) { if (vtype(boot_timeout) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "bootTimeoutMs must be a number"); double n = js_getnum(boot_timeout); if (n < 0 || n > UINT_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "bootTimeoutMs is out of range"); state->boot_timeout_ms = (unsigned int)n; }
ant_value_t cpu_time = js_get(js, opts, "cpuTimeMs"); if (vtype(cpu_time) != kTypeUndefined && vtype(cpu_time) != kTypeNull) { if (vtype(cpu_time) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "cpuTimeMs must be a number"); double n = js_getnum(cpu_time); if (!isfinite(n) || n < 0 || n > UINT_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "cpuTimeMs is out of range"); state->cpu_time_ms = (unsigned int)n; }
ant_value_t memory_mb = js_get(js, opts, "memoryMb"); ant_value_t memory = js_get(js, opts, "memory"); if (vtype(memory_mb) != kTypeUndefined && vtype(memory_mb) != kTypeNull) { if (vtype(memory_mb) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "memoryMb must be a number"); double n = js_getnum(memory_mb); long double bytes = (long double)n * 1024.0L * 1024.0L; if (!isfinite(n) || n <= 0 || bytes > (long double)ULLONG_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "memoryMb is out of range"); state->memory_size = (unsigned long long)bytes; } else if (vtype(memory) != kTypeUndefined && vtype(memory) != kTypeNull) { if (vtype(memory) == kTypeNumber) { double n = js_getnum(memory); if (!isfinite(n) || n <= 0 || (long double)n > (long double)ULLONG_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "memory is out of range"); state->memory_size = (unsigned long long)n; } else if (vtype(memory) == kTypeString) { const char *value = js_getstr(js, memory, NULL); if (!sandbox_parse_memory_size(value, &state->memory_size)) return js_mkerr_typed(js, JS_ERR_TYPE, "memory must be bytes or a size such as '256mb'"); } else return js_mkerr_typed(js, JS_ERR_TYPE, "memory must be a number or size string"); } if (state->memory_size < ANT_SANDBOX_MIN_MEMORY_SIZE) return js_mkerr_typed(js, JS_ERR_RANGE, "memory must be at least 64mb");
ant_value_t tty = js_get(js, opts, "tty"); if (vtype(tty) != kTypeUndefined && vtype(tty) != kTypeNull) { if (js_truthy(js, tty)) state->capabilities |= ANT_SANDBOX_CAP_STDOUT_TTY | ANT_SANDBOX_CAP_STDERR_TTY; else state->capabilities &= ~(ANT_SANDBOX_CAP_STDOUT_TTY | ANT_SANDBOX_CAP_STDERR_TTY); }
ant_value_t rows = js_get(js, opts, "ttyRows"); if (vtype(rows) != kTypeUndefined && vtype(rows) != kTypeNull) { if (vtype(rows) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "ttyRows must be a number"); double n = js_getnum(rows); if (n < 1 || n > UINT16_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "ttyRows is out of range"); state->tty_rows = (uint16_t)n; }
ant_value_t cols = js_get(js, opts, "ttyCols"); if (vtype(cols) != kTypeUndefined && vtype(cols) != kTypeNull) { if (vtype(cols) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "ttyCols must be a number"); double n = js_getnum(cols); if (n < 1 || n > UINT16_MAX) return js_mkerr_typed(js, JS_ERR_RANGE, "ttyCols is out of range"); state->tty_cols = (uint16_t)n; }
ant_value_t color = js_get(js, opts, "color"); if (vtype(color) != kTypeUndefined && vtype(color) != kTypeNull) { if (vtype(color) != kTypeString) return js_mkerr_typed(js, JS_ERR_TYPE, "color must be 'auto', 'force', 'strip', or 'preserve'"); const char *policy = js_getstr(js, color, NULL); if (strcmp(policy, "auto") == 0) { // keep the host-derived capability bits } else if (strcmp(policy, "force") == 0) { state->capabilities &= ~ANT_SANDBOX_CAP_COLOR_STRIP; state->capabilities |= ANT_SANDBOX_CAP_COLOR_FORCE; } else if (strcmp(policy, "strip") == 0) { state->capabilities &= ~ANT_SANDBOX_CAP_COLOR_FORCE; state->capabilities |= ANT_SANDBOX_CAP_COLOR_STRIP; } else if (strcmp(policy, "preserve") == 0) { state->capabilities &= ~(ANT_SANDBOX_CAP_COLOR_FORCE | ANT_SANDBOX_CAP_COLOR_STRIP); } else return js_mkerr_typed(js, JS_ERR_TYPE, "color must be 'auto', 'force', 'strip', or 'preserve'"); }
return js_mkundef();}
static ant_value_t sandbox_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox constructor requires 'new'");
if (ant_sandbox_is_guest_process()) return js_mkerr_typed(js, JS_ERR_TYPE, "Nested Sandbox creation is not supported inside ant sandbox");
sandbox_state_t *state = calloc(1, sizeof(*state)); if (!state) return js_mkerr(js, "out of memory"); state->js = js; state->self = js_mkundef(); state->run_promise = js_mkundef(); state->close_promise = js_mkundef(); if (pthread_mutex_init(&state->mutex, NULL) != 0) { free(state); return js_mkerr(js, "failed to initialize Sandbox"); } state->mutex_initialized = true;
ant_sandbox_launch_options_init(&state->launch); state->boot_timeout_ms = ANT_SANDBOX_DEFAULT_BOOT_TIMEOUT_MS; state->memory_size = ANT_SANDBOX_DEFAULT_MEMORY_SIZE; state->capabilities = ant_sandbox_terminal_capabilities(&state->tty_rows, &state->tty_cols);
char err[512] = { 0 }; int rc = ant_sandbox_assets_resolve(&state->assets, err, sizeof(err)); if (rc != 0) { sandbox_state_destroy(state); return js_mkerr_typed(js, JS_ERR_TYPE, "%s", err[0] ? err : "Failed to resolve sandbox assets"); }
ant_value_t result = sandbox_apply_options(js, state, nargs > 0 ? args[0] : js_mkundef()); if (is_err(result)) { sandbox_state_destroy(state); return result; }
if (!state->launch.explicit_mounts) { char cwd[4096]; if (!getcwd(cwd, sizeof(cwd))) { sandbox_state_destroy(state); return js_mkerr_typed(js, JS_ERR_TYPE, "failed to read current directory"); } rc = ant_sandbox_launch_add_default_mount(&state->launch, cwd, err, sizeof(err)); if (rc != 0) { sandbox_state_destroy(state); return js_mkerr_typed(js, JS_ERR_TYPE, "%s", err[0] ? err : "failed to add default mount"); } }
ant_value_t obj = Ant_Object_CreateBare(js); state->self = obj; ant_value_t proto = js_instance_proto_from_new_target(js, js->builtins.sandbox_proto, call_new_target); if (is_object_type(proto)) js_set_proto_init(obj, proto); js_set_native(obj, state, SANDBOX_NATIVE_TAG); js_set_finalizer(obj, sandbox_finalize); return obj;}
static ant_value_t sandbox_rejected(ant_t *js, ant_value_t error) { GC_ROOT_SAVE(root_mark, js); if (is_err(error)) error = js_take_thrown(js, error);
GC_ROOT_PIN(js, error); ant_value_t promise = js_mkpromise(js);
if (!is_err(promise)) js_reject_promise(js, promise, error); GC_ROOT_RESTORE(js, root_mark);
return promise;}
static sandbox_state_t *sandbox_require_open_state(ant_t *js, ant_value_t *error_out) { sandbox_state_t *state = sandbox_get_state(js->this_val); if (!state) { *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Sandbox"); return NULL; } if (state->closed) { *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox is closed"); return NULL; } return state;}
typedef struct { ant_t *js; bool has_result; bool has_error; ant_value_t result; ant_value_t error;} sandbox_frame_capture_t;
static bool sandbox_capture_frame(uint8_t type, const void *payload, size_t payload_len, void *user) { sandbox_frame_capture_t *capture = user; if (!capture || !capture->js) return false;
if (type == ANT_SANDBOX_FRAME_RESULT) { ant_value_t result = js_mkundef(); if (ant_sandbox_decode_result_value(capture->js, payload, payload_len, &result)) { capture->result = result; capture->has_result = true; return true; } return false; }
if (type == ANT_SANDBOX_FRAME_ERROR) { capture->error = ant_sandbox_decode_error_value(capture->js, payload, payload_len); capture->has_error = true; return true; }
return false;}
static void sandbox_fill_result_from_rc(ant_sandbox_vm_result_t *result, int rc) { if (!result || result->kind != ANT_SANDBOX_VM_RESULT_NONE) return; if (rc == 0) return; if (rc == -ENOSYS) result->kind = ANT_SANDBOX_VM_RESULT_BACKEND_UNAVAILABLE; else if (rc == -EINVAL) result->kind = ANT_SANDBOX_VM_RESULT_CONFIG_ERROR; else if (rc == -ETIMEDOUT) result->kind = ANT_SANDBOX_VM_RESULT_TIMEOUT; else if (rc == ANT_SANDBOX_CPU_TIME_LIMIT_CODE) result->kind = ANT_SANDBOX_VM_RESULT_CPU_TIME_LIMIT; else result->kind = ANT_SANDBOX_VM_RESULT_VM_ERROR; result->code = rc;}
static ant_value_t sandbox_vm_result_error(ant_t *js, const ant_sandbox_vm_result_t *result, int rc) { ant_sandbox_vm_result_t fallback = {0}; if (!result || result->kind == ANT_SANDBOX_VM_RESULT_NONE) { fallback.code = rc; sandbox_fill_result_from_rc(&fallback, rc); result = &fallback; }
const char *name = ant_sandbox_vm_result_name(result->kind); char message[256]; switch (result->kind) { case ANT_SANDBOX_VM_RESULT_GUEST_EXIT: snprintf(message, sizeof(message), "sandbox script exited with code %d", result->code); break; case ANT_SANDBOX_VM_RESULT_BACKEND_UNAVAILABLE: #if defined(__APPLE__) && !defined(__aarch64__) snprintf(message, sizeof(message), "sandbox VM backend requires Apple Silicon"); #elif defined(_WIN32) snprintf( message, sizeof(message), "native Windows sandbox backend is not implemented; use WSL for sandbox support" ); #else snprintf(message, sizeof(message), "sandbox VM backend is not available"); #endif break; case ANT_SANDBOX_VM_RESULT_CONFIG_ERROR: snprintf(message, sizeof(message), "sandbox VM configuration failed (%d)", result->code); break; case ANT_SANDBOX_VM_RESULT_TIMEOUT: snprintf(message, sizeof(message), "sandbox VM timed out"); break; case ANT_SANDBOX_VM_RESULT_CPU_TIME_LIMIT: snprintf(message, sizeof(message), "sandbox VM exceeded its CPU time budget"); break; case ANT_SANDBOX_VM_RESULT_KERNEL_PANIC: snprintf(message, sizeof(message), "sandbox kernel panic"); break; case ANT_SANDBOX_VM_RESULT_PROTOCOL_ERROR: snprintf(message, sizeof(message), "sandbox daemon protocol error (%d)", result->code); break; case ANT_SANDBOX_VM_RESULT_TRANSPORT_ERROR: snprintf(message, sizeof(message), "sandbox transport error (%d)", result->code); break; case ANT_SANDBOX_VM_RESULT_CANCELED: snprintf(message, sizeof(message), "sandbox VM canceled"); break; case ANT_SANDBOX_VM_RESULT_VM_ERROR: case ANT_SANDBOX_VM_RESULT_NONE: default: snprintf(message, sizeof(message), "sandbox VM failed (%d)", result->code ? result->code : rc); break; }
ant_value_t err = Ant_Error_Create(js, JS_ERR_TYPE | JS_ERR_NO_STACK, message); GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, err); js_set(js, err, "name", js_mkstr(js, name, strlen(name))); js_set(js, err, "code", js_mknum((double)result->code)); GC_ROOT_RESTORE(js, root_mark); return err;}
static int sandbox_ensure_session(ant_t *js, sandbox_state_t *state, ant_sandbox_vm_result_t *result) { if (state->session) return 0;
ant_sandbox_vm_config_t config = { .image_path = state->assets.image, .kernel_path = state->assets.kernel, .capabilities = state->capabilities, .mounts = state->launch.mounts, .mount_count = state->launch.mount_count, .network_enabled = true, .forwards = state->launch.forwards, .forward_count = state->launch.forward_count, .cpu_count = 1, .memory_size = state->memory_size, .timeout_ms = state->timeout_ms, .boot_timeout_ms = state->boot_timeout_ms, .cpu_time_ms = state->cpu_time_ms, .verbose = state->verbose, .result = result, };
return ant_sandbox_vm_session_create(&config, &state->session);}
static ant_value_t sandbox_execute_request( ant_t *js, sandbox_state_t *state, uint8_t *request, size_t request_len, bool expect_result) { if (!request) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "failed to build sandbox request"));
ant_sandbox_vm_result_t vm_result = {0}; int rc = sandbox_ensure_session(js, state, &vm_result); if (rc != 0) { free(request); sandbox_fill_result_from_rc(&vm_result, rc); return sandbox_rejected(js, sandbox_vm_result_error(js, &vm_result, rc)); }
sandbox_frame_capture_t capture = { .js = js, .result = js_mkundef(), .error = js_mkundef(), };
ant_sandbox_vm_request_t vm_request = { .request_data = request, .request_len = request_len, .frame_handler = sandbox_capture_frame, .frame_handler_user = &capture, .result = &vm_result, };
rc = ant_sandbox_vm_session_execute(state->session, &vm_request); sandbox_fill_result_from_rc(&vm_result, rc); free(request);
GC_ROOT_SAVE(root_mark, js); if (capture.has_result) GC_ROOT_PIN(js, capture.result); if (capture.has_error) GC_ROOT_PIN(js, capture.error);
ant_value_t promise = js_mkpromise(js); GC_ROOT_PIN(js, promise); if (capture.has_error) js_reject_promise(js, promise, capture.error); else if (ant_sandbox_vm_result_is_infrastructure_failure(&vm_result)) js_reject_promise(js, promise, sandbox_vm_result_error(js, &vm_result, rc)); else if (vm_result.kind == ANT_SANDBOX_VM_RESULT_GUEST_EXIT && vm_result.code != 0) js_reject_promise(js, promise, sandbox_vm_result_error(js, &vm_result, rc)); else if (rc != 0) js_reject_promise(js, promise, sandbox_vm_result_error(js, &vm_result, rc)); else if (expect_result) js_resolve_promise(js, promise, capture.has_result ? capture.result : js_mkundef()); else js_resolve_promise(js, promise, js_mknum(0)); GC_ROOT_RESTORE(js, root_mark); return promise;}
static void sandbox_host_deliver_message(sandbox_state_t *state, const char *payload, size_t payload_len) { if (!state || !state->js || !is_object_type(state->self)) return; ant_t *js = state->js; ant_value_t encoded = js_mkstr(js, payload, payload_len); ant_value_t message = json_parse_value(js, encoded);
if (is_err(message)) { js_take_thrown(js, message); return; }
sandbox_message_waiter_t **link = &state->waiter_head; while (*link) { sandbox_message_waiter_t *waiter = *link; bool matches = waiter->type == NULL; if (waiter->type) { ant_value_t type = is_object_type(message) ? js_get(js, message, "type") : js_mkundef(); if (vtype(type) == kTypeString) { const char *value = js_getstr(js, type, NULL); matches = value && strcmp(value, waiter->type) == 0; } } if (!matches) { link = &waiter->next; continue; }
*link = waiter->next; ant_value_t value = waiter->iterator ? js_iter_result(js, true, message) : message; js_resolve_promise(js, waiter->promise, value); free(waiter->type); free(waiter); } state->waiter_tail = NULL; for (sandbox_message_waiter_t *waiter = state->waiter_head; waiter; waiter = waiter->next) state->waiter_tail = waiter;
ant_value_t handler = js_get(js, state->self, "onmessage"); if (!is_callable(handler)) handler = js_get(js, state->self, "_messageHandler"); if (!is_callable(handler)) return; ant_value_t args[1] = { message }; sv_vm_call(js->vm, js, handler, state->self, args, 1, NULL, js_mkundef());}
static void sandbox_finish_waiters(sandbox_state_t *state) { if (!state || !state->js) return; ant_t *js = state->js;
sandbox_message_waiter_t *waiter = state->waiter_head; state->waiter_head = state->waiter_tail = NULL;
while (waiter) { sandbox_message_waiter_t *next = waiter->next;
if (waiter->iterator) js_resolve_promise(js, waiter->promise, js_iter_result(js, false, js_mkundef())); else js_reject_promise(js, waiter->promise, Ant_Error_Create(js, JS_ERR_TYPE, "Sandbox message channel closed"));
free(waiter->type); free(waiter); waiter = next; }}
static bool sandbox_async_capture_frame(uint8_t type, const void *payload, size_t payload_len, void *user) { sandbox_state_t *state = user; if (!state || type != ANT_SANDBOX_FRAME_MESSAGE) return false; sandbox_message_node_t *node = calloc(1, sizeof(*node)); if (!node) return true; node->data = malloc(payload_len + 1); if (!node->data) { free(node); return true; } if (payload_len > 0) memcpy(node->data, payload, payload_len); node->data[payload_len] = '\0'; node->len = payload_len;
pthread_mutex_lock(&state->mutex); if (state->message_tail) state->message_tail->next = node; else state->message_head = node; state->message_tail = node; pthread_mutex_unlock(&state->mutex); if (state->async_initialized) uv_async_send(&state->async); return true;}
static void *sandbox_run_worker(void *user) { sandbox_state_t *state = user; ant_sandbox_vm_request_t request = { .request_data = state->run_request, .request_len = state->run_request_len, .frame_handler = sandbox_async_capture_frame, .frame_handler_user = state, .result = &state->run_result, }; int rc = ant_sandbox_vm_session_execute(state->session, &request); sandbox_fill_result_from_rc(&state->run_result, rc);
pthread_mutex_lock(&state->mutex); state->run_rc = rc; state->run_done = true; pthread_mutex_unlock(&state->mutex); if (state->async_initialized) uv_async_send(&state->async); return NULL;}
static void sandbox_async_closed(uv_handle_t *handle) { sandbox_state_t *state = handle ? handle->data : NULL; if (!state) return; state->async_initialized = false; sandbox_remove_active(state);}
static void sandbox_async_cb(uv_async_t *handle) { sandbox_state_t *state = handle ? handle->data : NULL; if (!state || !state->js) return; ant_t *js = state->js;
pthread_mutex_lock(&state->mutex); sandbox_message_node_t *messages = state->message_head; state->message_head = state->message_tail = NULL; bool done = state->run_done; pthread_mutex_unlock(&state->mutex);
while (messages) { sandbox_message_node_t *next = messages->next; sandbox_host_deliver_message(state, messages->data, messages->len); free(messages->data); free(messages); messages = next; }
if (!done) { js_maybe_drain_microtasks_after_async_settle(js); return; }
if (state->worker_started) { pthread_join(state->worker, NULL); state->worker_started = false; } free(state->run_request); state->run_request = NULL; state->run_request_len = 0; state->running = false; sandbox_finish_waiters(state);
if (state->has_run_promise) { if (ant_sandbox_vm_result_is_infrastructure_failure(&state->run_result) || state->run_rc != 0) js_reject_promise(js, state->run_promise, sandbox_vm_result_error(js, &state->run_result, state->run_rc)); else js_resolve_promise(js, state->run_promise, js_mknum(0)); state->has_run_promise = false; state->run_promise = js_mkundef(); }
if (state->close_requested) { size_t close_len = 0; uint8_t *close_request = ant_sandbox_build_close_request_frame(&close_len); ant_sandbox_vm_result_t close_result = {0}; int close_rc = -ENOMEM; if (close_request) { ant_sandbox_vm_request_t request = { .request_data = close_request, .request_len = close_len, .result = &close_result, }; close_rc = ant_sandbox_vm_session_execute(state->session, &request); free(close_request); } sandbox_update_stats(state); ant_sandbox_vm_session_destroy(state->session); state->session = NULL; ant_sandbox_launch_options_cleanup(&state->launch); if (state->has_close_promise) { if (close_rc == 0) js_resolve_promise(js, state->close_promise, js_mkundef()); else js_reject_promise(js, state->close_promise, sandbox_vm_result_error(js, &close_result, close_rc)); state->has_close_promise = false; state->close_promise = js_mkundef(); } }
js_maybe_drain_microtasks_after_async_settle(js); if (state->async_initialized && !uv_is_closing((uv_handle_t *)&state->async)) uv_close((uv_handle_t *)&state->async, sandbox_async_closed);}
static ant_value_t sandbox_run(ant_params_t) { ant_value_t state_error = js_mkundef(); sandbox_state_t *state = sandbox_require_open_state(js, &state_error); if (!state) return sandbox_rejected(js, state_error); if (state->running) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox already has a running entry")); if (state->async_initialized) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox is still finishing its previous entry")); if (nargs < 1 || vtype(args[0]) != kTypeString) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.run(entry, argv?) requires an entry string"));
char *entry = js_getstr(js, args[0], NULL); if (!entry) return sandbox_rejected(js, js_mkerr(js, "out of memory")); char **argv = NULL; int argc = 0;
// TODO: reduce nesting split into helpers if (nargs >= 2 && vtype(args[1]) != kTypeUndefined && vtype(args[1]) != kTypeNull) { if (js_is_array(args[1])) { ant_offset_t len = js_arr_len(js, args[1]); if (len > INT32_MAX) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_RANGE, "argv is too large")); argv = calloc((size_t)len + 1, sizeof(*argv)); if (!argv) return sandbox_rejected(js, js_mkerr(js, "out of memory")); argc = (int)len; for (ant_offset_t i = 0; i < len; i++) { ant_value_t item = js_arr_get(js, args[1], i); if (vtype(item) != kTypeString) { free(argv); return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "argv entries must be strings")); } argv[i] = js_getstr(js, item, NULL); if (!argv[i]) { free(argv); return sandbox_rejected(js, js_mkerr(js, "out of memory")); } } } else { argc = nargs - 1; argv = calloc((size_t)argc + 1, sizeof(*argv)); if (!argv) return sandbox_rejected(js, js_mkerr(js, "out of memory")); for (int i = 0; i < argc; i++) { if (vtype(args[i + 1]) != kTypeString) { free(argv); return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "argv entries must be strings")); } argv[i] = js_getstr(js, args[i + 1], NULL); if (!argv[i]) { free(argv); return sandbox_rejected(js, js_mkerr(js, "out of memory")); } } } }
uint16_t forward_ports[ANT_SANDBOX_MAX_FORWARDS]; for (size_t i = 0; i < state->launch.forward_count; i++) forward_ports[i] = state->launch.forwards[i].guest_port;
size_t request_len = 0; uint8_t *request = ant_sandbox_build_run_request_frame( state->launch.guest_cwd, entry, argc, argv, state->capabilities, state->tty_rows, state->tty_cols, forward_ports, (uint32_t)state->launch.forward_count, &request_len ); free(argv); ant_sandbox_vm_result_t create_result = {0}; int rc = sandbox_ensure_session(js, state, &create_result); if (rc != 0) { free(request); sandbox_fill_result_from_rc(&create_result, rc); return sandbox_rejected(js, sandbox_vm_result_error(js, &create_result, rc)); }
state->run_request = request; state->run_request_len = request_len; state->run_done = false; state->run_rc = 0; memset(&state->run_result, 0, sizeof(state->run_result)); state->run_promise = js_mkpromise(js); state->has_run_promise = true; state->running = true;
if (uv_async_init(js->loop, &state->async, sandbox_async_cb) != 0) { state->running = false; state->has_run_promise = false; free(state->run_request); state->run_request = NULL; return sandbox_rejected(js, js_mkerr(js, "failed to initialize sandbox message channel")); } state->async.data = state; state->async_initialized = true; sandbox_add_active(state);
int thread_rc = pthread_create(&state->worker, NULL, sandbox_run_worker, state); if (thread_rc != 0) { state->running = false; state->has_run_promise = false; free(state->run_request); state->run_request = NULL; uv_close((uv_handle_t *)&state->async, sandbox_async_closed); return sandbox_rejected(js, js_mkerr(js, "failed to start sandbox worker")); } state->worker_started = true; return state->run_promise;}
static ant_value_t sandbox_eval(ant_params_t) { ant_value_t state_error = js_mkundef(); sandbox_state_t *state = sandbox_require_open_state(js, &state_error); if (!state) return sandbox_rejected(js, state_error); if (state->running) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.eval() is unavailable while an entry is running")); if (nargs < 1 || vtype(args[0]) != kTypeString) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.eval(source) requires a source string"));
size_t request_len = 0; uint8_t *request = ant_sandbox_build_eval_request_frame( state->launch.guest_cwd, js_getstr(js, args[0], NULL), state->capabilities, state->tty_rows, state->tty_cols, &request_len ); return sandbox_execute_request(js, state, request, request_len, true);}
static ant_value_t sandbox_close(ant_params_t) { sandbox_state_t *state = sandbox_get_state(js->this_val); ant_value_t promise = js_mkpromise(js); if (!state || state->closed) { js_resolve_promise(js, promise, js_mkundef()); return promise; }
state->closed = true; if (state->running) { size_t request_len = 0; uint8_t *request = ant_sandbox_build_close_request_frame(&request_len); if (!request) { js_reject_promise(js, promise, js_mkerr(js, "failed to build sandbox close request")); return promise; } int rc = ant_sandbox_vm_session_send(state->session, request, request_len); free(request); if (rc != 0) { state->closed = false; js_reject_promise(js, promise, js_mkerr(js, "failed to close running sandbox")); return promise; } state->close_requested = true; state->close_promise = promise; state->has_close_promise = true; return promise; } if (!state->session) { sandbox_finish_waiters(state); ant_sandbox_launch_options_cleanup(&state->launch); js_resolve_promise(js, promise, js_mkundef()); return promise; }
size_t request_len = 0; uint8_t *request = ant_sandbox_build_close_request_frame(&request_len); if (!request) { sandbox_update_stats(state); ant_sandbox_vm_session_destroy(state->session); state->session = NULL; sandbox_finish_waiters(state); ant_sandbox_launch_options_cleanup(&state->launch); js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "failed to build sandbox close request")); return promise; }
ant_sandbox_vm_result_t vm_result = {0}; ant_sandbox_vm_request_t vm_request = { .request_data = request, .request_len = request_len, .result = &vm_result, }; int rc = ant_sandbox_vm_session_execute(state->session, &vm_request); sandbox_fill_result_from_rc(&vm_result, rc); free(request); sandbox_update_stats(state); ant_sandbox_vm_session_destroy(state->session); state->session = NULL; sandbox_finish_waiters(state); ant_sandbox_launch_options_cleanup(&state->launch);
if (rc == 0) js_resolve_promise(js, promise, js_mkundef()); else js_reject_promise(js, promise, sandbox_vm_result_error(js, &vm_result, rc)); return promise;}
static ant_value_t sandbox_send(ant_params_t) { sandbox_state_t *state = sandbox_get_state(js->this_val); if (!state || state->closed) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox is closed"); if (!state->running || !state->session) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.send(value) requires a running entry"); if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.send(value) requires a value");
ant_value_t encoded = json_stringify_value(js, args[0]); if (is_err(encoded)) return encoded; size_t payload_len = 0; const char *payload = js_getstr(js, encoded, &payload_len); size_t frame_len = 0; uint8_t *frame = ant_sandbox_build_frame(ANT_SANDBOX_FRAME_MESSAGE, payload, payload_len, &frame_len); if (!frame) return js_mkerr(js, "failed to encode sandbox message"); int rc = ant_sandbox_vm_session_send(state->session, frame, frame_len); free(frame); if (rc != 0) return js_mkerr(js, "failed to send sandbox message"); return js_mkundef();}
static ant_value_t sandbox_wait_for_message( ant_t *js, sandbox_state_t *state, const char *type, bool iterator) { ant_value_t promise = js_mkpromise(js); if (!state || state->closed) { if (iterator) js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); else js_reject_promise( js, promise, Ant_Error_Create(js, JS_ERR_TYPE, "Sandbox message channel is closed") ); return promise; } if (!state->running) { if (iterator) js_resolve_promise(js, promise, js_iter_result(js, false, js_mkundef())); else js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox message channel requires a running entry")); return promise; }
sandbox_message_waiter_t *waiter = calloc(1, sizeof(*waiter)); if (!waiter) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; } if (type) { waiter->type = strdup(type); if (!waiter->type) { free(waiter); js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; } } waiter->promise = promise; waiter->iterator = iterator; if (state->waiter_tail) state->waiter_tail->next = waiter; else state->waiter_head = waiter; state->waiter_tail = waiter; sandbox_add_active(state); return promise;}
static ant_value_t sandbox_receive(ant_params_t) { (void)args; (void)nargs; return sandbox_wait_for_message(js, sandbox_get_state(js->this_val), NULL, false);}
static ant_value_t sandbox_once(ant_params_t) { sandbox_state_t *state = sandbox_get_state(js->this_val); if (nargs < 1 || vtype(args[0]) != kTypeString) return sandbox_rejected(js, js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.once(type) requires a message type string")); const char *type = js_getstr(js, args[0], NULL); if (!type) return sandbox_rejected(js, js_mkerr(js, "out of memory")); return sandbox_wait_for_message(js, state, type, false);}
static ant_value_t sandbox_messages_next(ant_params_t) { return sandbox_wait_for_message(js, sandbox_get_state(js->this_val), NULL, true);}
static ant_value_t sandbox_messages_getter(ant_params_t) { return js->this_val;}
static ant_value_t sandbox_stats(ant_params_t) { (void)args; (void)nargs; sandbox_state_t *state = sandbox_get_state(js->this_val); if (!state) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Sandbox receiver");
ant_sandbox_vm_stats_t stats = state->last_stats; if (state->session) { ant_sandbox_vm_stats_t current; if (ant_sandbox_vm_session_stats(state->session, ¤t) == 0) { state->last_stats = current; stats = current; } }
ant_value_t result = Ant_Object_CreateBare(js); js_set(js, result, "cpuTimeMs", js_mknum((double)stats.cpu_time_ns / 1000000.0)); js_set(js, result, "wallTimeMs", js_mknum((double)stats.wall_time_ns / 1000000.0)); if (stats.resident_memory_available) js_set(js, result, "residentMemory", js_mknum((double)stats.resident_memory_bytes)); return result;}
static ant_value_t sandbox_on(ant_params_t) { if (nargs < 2 || vtype(args[0]) != kTypeString || !is_callable(args[1])) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox.on('message', handler) requires a handler"); const char *event = js_getstr(js, args[0], NULL); if (!event || strcmp(event, "message") != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "Sandbox only supports the 'message' event"); js_set(js, js->this_val, "_messageHandler", args[1]); return js->this_val;}
static ant_value_t sandbox_terminate(ant_params_t) { sandbox_state_t *state = sandbox_get_state(js->this_val); ant_value_t promise = js_mkpromise(js); if (!state || state->closed) { js_resolve_promise(js, promise, js_mkundef()); return promise; }
state->closed = true; int rc = 0; if (state->session) { sandbox_update_stats(state); rc = ant_sandbox_vm_session_cancel(state->session); if (state->worker_started) { pthread_join(state->worker, NULL); state->worker_started = false; state->running = false; } ant_sandbox_vm_session_destroy(state->session); state->session = NULL; } ant_sandbox_launch_options_cleanup(&state->launch);
sandbox_finish_waiters(state);
if (rc == 0) js_resolve_promise(js, promise, js_mkundef()); else { ant_sandbox_vm_result_t result = { .kind = rc == -ENOSYS ? ANT_SANDBOX_VM_RESULT_BACKEND_UNAVAILABLE : ANT_SANDBOX_VM_RESULT_CANCELED, .code = rc, }; js_reject_promise(js, promise, sandbox_vm_result_error(js, &result, rc)); } return promise;}
void gc_mark_sandbox(ant_t *js, gc_mark_fn mark) { for (sandbox_state_t *state = js->sandboxes; state; state = state->next) { mark(js, state->self); if (state->has_run_promise) mark(js, state->run_promise); if (state->has_close_promise) mark(js, state->close_promise); for (sandbox_message_waiter_t *waiter = state->waiter_head; waiter; waiter = waiter->next) mark(js, waiter->promise); } if (g_guest_port.js == js && is_object_type(g_guest_port.obj)) mark(js, g_guest_port.obj);}
ant_value_t sandbox_library(ant_t *js) { if (!js->builtins.sandbox_ctor) { js->builtins.sandbox_proto = Ant_Object_CreateBare(js); js_set(js, js->builtins.sandbox_proto, "run", Ant_Function_CreateWithArity(sandbox_run, 1)); js_set(js, js->builtins.sandbox_proto, "eval", Ant_Function_CreateWithArity(sandbox_eval, 1)); js_set(js, js->builtins.sandbox_proto, "send", Ant_Function_CreateWithArity(sandbox_send, 1)); js_set(js, js->builtins.sandbox_proto, "receive", Ant_Function_Create(sandbox_receive)); js_set(js, js->builtins.sandbox_proto, "once", Ant_Function_CreateWithArity(sandbox_once, 1)); js_set(js, js->builtins.sandbox_proto, "next", Ant_Function_Create(sandbox_messages_next)); js_set(js, js->builtins.sandbox_proto, "stats", Ant_Function_Create(sandbox_stats)); js_set(js, js->builtins.sandbox_proto, "on", Ant_Function_CreateWithArity(sandbox_on, 2)); js_set(js, js->builtins.sandbox_proto, "close", Ant_Function_Create(sandbox_close)); js_set(js, js->builtins.sandbox_proto, "terminate", Ant_Function_Create(sandbox_terminate)); js_set_getter_desc(js, js->builtins.sandbox_proto, "messages", 8, Ant_Function_Create(sandbox_messages_getter), JS_DESC_C); js_set_sym(js, js->builtins.sandbox_proto, js->sym.asyncIterator_sym, Ant_Function_Create(sym_this_cb)); js_set_tostringtag(js, js->builtins.sandbox_proto, js_mkstr(js, "Sandbox", 7)); js->builtins.sandbox_ctor = Ant_Function_CreateConstructor(js, sandbox_ctor, js->builtins.sandbox_proto, "Sandbox", 0); }
ant_value_t lib = Ant_Object_CreateBare(js); js_set(js, lib, "Sandbox", js->builtins.sandbox_ctor); js_set(js, lib, "parentPort", sandbox_guest_parent_port(js)); js_set(js, lib, "default", lib); js_set_slot_wb(js, lib, SLOT_DEFAULT, lib); js_set_tostringtag(js, lib, js_mkstr(js, "sandbox", 7)); return lib;}