MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve memory safety in core runtime

+163 -21
+12 -3
include/arena.h
··· 11 11 #else 12 12 #include <sys/mman.h> 13 13 #include <unistd.h> 14 + #include <errno.h> 14 15 #endif 15 16 16 17 #define ANT_ARENA_MIN (32 * 1024) ··· 61 62 62 63 static inline int ant_arena_commit(void *base, size_t old_size, size_t new_size) { 63 64 if (new_size <= old_size) return 0; 64 - size_t page_size = (size_t)sysconf(_SC_PAGESIZE); 65 - size_t old_pages = (old_size + page_size - 1) & ~(page_size - 1); 66 - size_t new_pages = (new_size + page_size - 1) & ~(page_size - 1); 65 + 66 + long page_size_long = sysconf(_SC_PAGESIZE); 67 + if (page_size_long <= 0) { 68 + errno = (page_size_long == -1 && errno == 0) ? EINVAL : errno; 69 + return -1; 70 + } 71 + 72 + size_t page_size = (size_t)page_size_long; 73 + size_t old_pages = ((old_size + page_size - 1) / page_size) * page_size; 74 + size_t new_pages = ((new_size + page_size - 1) / page_size) * page_size; 75 + 67 76 if (new_pages <= old_pages) return 0; 68 77 return mprotect((char *)base + old_pages, new_pages - old_pages, PROT_READ | PROT_WRITE); 69 78 }
+10 -3
src/errors.c
··· 50 50 51 51 __attribute__((format(printf, 3, 4))) 52 52 static size_t append_errmsg_fmt(struct js *js, size_t used, const char *fmt, ...) { 53 + int max_attempts = 3; 54 + int attempt = 0; 55 + 53 56 for (;;) { 54 - if (!ensure_errmsg_capacity(js, used + 1)) return used; 57 + if (!ensure_errmsg_capacity(js, used + 1)) { 58 + return js->errmsg_size ? js->errmsg_size - 1 : used; 59 + } 55 60 56 61 size_t remaining = js->errmsg_size - used; 57 62 va_list ap; ··· 63 68 if ((size_t)written < remaining) return used + (size_t)written; 64 69 65 70 if (!ensure_errmsg_capacity(js, used + (size_t)written + 1)) { 66 - return js->errmsg_size ? js->errmsg_size - 1 : used; 67 - } 71 + if (++attempt >= max_attempts) { 72 + return js->errmsg_size ? js->errmsg_size - 1 : used; 73 + } 74 + } else attempt = 0; 68 75 } 69 76 } 70 77
+4 -1
src/roots.c
··· 7 7 if (js->gc_roots_len >= js->gc_roots_cap) { 8 8 int new_cap = js->gc_roots_cap ? js->gc_roots_cap * 2 : GC_ROOTS_INITIAL_CAP; 9 9 jsval_t *new_roots = ant_realloc(js->gc_roots, new_cap * sizeof(jsval_t)); 10 - if (!new_roots) return -1; 10 + if (!new_roots) { 11 + fprintf(stderr, "FATAL: Out of memory allocating GC roots\n"); 12 + abort(); 13 + } 11 14 js->gc_roots = new_roots; 12 15 js->gc_roots_cap = new_cap; 13 16 }
+25 -14
src/stack.c
··· 3 3 4 4 #include <stdint.h> 5 5 #include <stdlib.h> 6 + #include <string.h> 6 7 7 8 call_stack_t global_call_stack = {NULL, 0, 0}; 8 9 9 10 void pop_call_frame(void) { 10 - if (global_call_stack.depth > 0) global_call_stack.depth--; 11 + if (global_call_stack.depth == 0) return; 12 + call_frame_t *frame = &global_call_stack.frames[--global_call_stack.depth]; 13 + free((void *)frame->function_name); 11 14 } 12 15 13 16 bool push_call_frame(const char *filename, const char *function_name, const char *code, uint32_t pos) { 14 17 if (global_call_stack.depth >= global_call_stack.capacity) { 15 - int new_capacity = global_call_stack.capacity == 0 ? 32 : global_call_stack.capacity * 2; 16 - if ((size_t)new_capacity > SIZE_MAX / sizeof(call_frame_t)) return false; 17 - 18 - call_frame_t *new_stack = (call_frame_t *)ant_realloc(global_call_stack.frames, new_capacity * sizeof(call_frame_t)); 19 - if (!new_stack) return false; 20 - 18 + size_t new_capacity = global_call_stack.capacity == 0 ? 32U : (size_t)global_call_stack.capacity * 2U; 19 + if (new_capacity > SIZE_MAX / sizeof(call_frame_t)) abort(); 20 + 21 + size_t alloc_size = new_capacity * sizeof(call_frame_t); 22 + call_frame_t *new_stack = (call_frame_t *)ant_realloc(global_call_stack.frames, alloc_size); 23 + if (!new_stack) abort(); 24 + 21 25 global_call_stack.frames = new_stack; 22 - global_call_stack.capacity = new_capacity; 26 + global_call_stack.capacity = (int)new_capacity; 23 27 } 24 28 25 - call_frame_t *frame = &global_call_stack.frames[global_call_stack.depth++]; 29 + char *func_name_copy = NULL; 30 + if (function_name) { 31 + size_t len = strlen(function_name) + 1; 32 + func_name_copy = (char *)ant_realloc(NULL, len); 33 + if (!func_name_copy) abort(); 34 + memcpy(func_name_copy, function_name, len); 35 + } 36 + 37 + call_frame_t *frame = 38 + &global_call_stack.frames[global_call_stack.depth++]; 26 39 27 40 *frame = (call_frame_t) { 28 41 .filename = filename, 29 - .function_name = function_name, 42 + .function_name = func_name_copy, 30 43 .code = code, 31 - .pos = pos, 32 - .line = -1, 33 - .col = -1, 44 + .pos = pos, .line = -1, .col = -1, 34 45 }; 35 46 36 47 return true; 37 - } 48 + }
+53
tests/test_gc_async.js
··· 1 + // Async GC stress: coroutine-heavy allocation without timers 2 + 3 + function stripAnsi(str) { 4 + return str.replace(/\x1b\[[0-9;]*m/g, ''); 5 + } 6 + 7 + function pad(str, len) { 8 + const visible = stripAnsi(str).length; 9 + const diff = len - visible; 10 + if (diff <= 0) return str; 11 + return str + ' '.repeat(diff); 12 + } 13 + 14 + function render() { 15 + const width = 120; 16 + const height = 40; 17 + const lines = []; 18 + 19 + for (let y = 0; y < height; y++) { 20 + lines.push(' '.repeat(width)); 21 + } 22 + 23 + for (let y = 0; y < height; y++) { 24 + let text = `\x1b[38;5;196mRow ${y}\x1b[0m: `; 25 + text += `\x1b[38;5;82m${'โ–ˆ'.repeat(20)}\x1b[0m`; 26 + text += `\x1b[2m${'โ–‘'.repeat(20)}\x1b[0m`; 27 + text = pad(text, width); 28 + lines[y] = text; 29 + } 30 + 31 + return lines.join('\n'); 32 + } 33 + 34 + async function handleEvent(n) { 35 + for (let i = 0; i < 10; i++) { 36 + render(); 37 + } 38 + } 39 + 40 + for (let count = 1; count <= 500; count++) { 41 + handleEvent(count); 42 + const stats = Ant.stats(); 43 + console.log( 44 + `tick ${count}: arena ${(stats.arenaUsed / 1024 / 1024).toFixed(1)}MB / ` + 45 + `${(stats.arenaSize / 1024 / 1024).toFixed(1)}MB, ` + 46 + `rss ${(stats.rss / 1024 / 1024).toFixed(1)}MB` 47 + ); 48 + } 49 + 50 + console.log('Done - forcing GC...'); 51 + Ant.gc(); 52 + const after = Ant.stats(); 53 + console.log(`after GC: arena ${(after.arenaUsed / 1024 / 1024).toFixed(1)}MB`);
+59
tests/test_gc_timer.js
··· 1 + // Timer GC stress: interval-driven allocation without coroutines 2 + 3 + function stripAnsi(str) { 4 + return str.replace(/\x1b\[[0-9;]*m/g, ''); 5 + } 6 + 7 + function pad(str, len) { 8 + const visible = stripAnsi(str).length; 9 + const diff = len - visible; 10 + if (diff <= 0) return str; 11 + return str + ' '.repeat(diff); 12 + } 13 + 14 + function render() { 15 + const width = 120; 16 + const height = 40; 17 + const lines = []; 18 + 19 + for (let y = 0; y < height; y++) { 20 + lines.push(' '.repeat(width)); 21 + } 22 + 23 + for (let y = 0; y < height; y++) { 24 + let text = `\x1b[38;5;196mRow ${y}\x1b[0m: `; 25 + text += `\x1b[38;5;82m${'โ–ˆ'.repeat(20)}\x1b[0m`; 26 + text += `\x1b[2m${'โ–‘'.repeat(20)}\x1b[0m`; 27 + text = pad(text, width); 28 + lines[y] = text; 29 + } 30 + 31 + return lines.join('\n'); 32 + } 33 + 34 + function handleEvent(n) { 35 + for (let i = 0; i < 10; i++) { 36 + render(); 37 + } 38 + } 39 + 40 + let count = 0; 41 + const interval = setInterval(() => { 42 + count++; 43 + handleEvent(count); 44 + 45 + const stats = Ant.stats(); 46 + console.log( 47 + `tick ${count}: arena ${(stats.arenaUsed / 1024 / 1024).toFixed(1)}MB / ` + 48 + `${(stats.arenaSize / 1024 / 1024).toFixed(1)}MB, ` + 49 + `rss ${(stats.rss / 1024 / 1024).toFixed(1)}MB` 50 + ); 51 + 52 + if (count >= 500) { 53 + clearInterval(interval); 54 + console.log('Done - forcing GC...'); 55 + Ant.gc(); 56 + const after = Ant.stats(); 57 + console.log(`after GC: arena ${(after.arenaUsed / 1024 / 1024).toFixed(1)}MB`); 58 + } 59 + }, 10);