diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c875751 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# hide all tests +tests/** linguist-generated=true \ No newline at end of file diff --git a/meson.build b/meson.build index 0d45371..2907bc2 100644 --- a/meson.build +++ b/meson.build @@ -43,7 +43,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.6.12') +version_conf.set('ANT_VERSION', '0.0.6.13') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index baf5b9e..414353c 100644 --- a/src/ant.c +++ b/src/ant.c @@ -560,7 +560,32 @@ static size_t cpy(char *dst, size_t dstlen, const char *src, size_t srclen) { return i; } +// Circular reference tracking for stringification +#define MAX_STRINGIFY_DEPTH 64 +static jsval_t stringify_stack[MAX_STRINGIFY_DEPTH]; +static int stringify_depth = 0; + +static bool is_circular(jsval_t obj) { + for (int i = 0; i < stringify_depth; i++) { + if (stringify_stack[i] == obj) return true; + } + return false; +} + +static void push_stringify(jsval_t obj) { + if (stringify_depth < MAX_STRINGIFY_DEPTH) { + stringify_stack[stringify_depth++] = obj; + } +} + +static void pop_stringify(void) { + if (stringify_depth > 0) stringify_depth--; +} + static size_t strarr(struct js *js, jsval_t obj, char *buf, size_t len) { + if (is_circular(obj)) return cpy(buf, len, "[Circular]", 10); + + push_stringify(obj); size_t n = cpy(buf, len, "[", 1); jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | CONSTMASK); jsoff_t length = 0; @@ -609,7 +634,9 @@ static size_t strarr(struct js *js, jsval_t obj, char *buf, size_t len) { } } - return n + cpy(buf + n, len - n, "]", 1); + n += cpy(buf + n, len - n, "]", 1); + pop_stringify(); + return n; } static size_t strdate(struct js *js, jsval_t obj, char *buf, size_t len) { @@ -647,6 +674,9 @@ static size_t strobj(struct js *js, jsval_t obj, char *buf, size_t len) { jsoff_t time_off = lkp(js, obj, "__time", 6); if (time_off != 0) return strdate(js, obj, buf, len); + if (is_circular(obj)) return cpy(buf, len, "[Circular]", 10); + + push_stringify(obj); size_t n = cpy(buf, len, "{", 1); jsoff_t next = loadoff(js, (jsoff_t) vdata(obj)) & ~(3U | CONSTMASK); @@ -660,7 +690,9 @@ static size_t strobj(struct js *js, jsval_t obj, char *buf, size_t len) { next = loadoff(js, next) & ~(3U | CONSTMASK); } - return n + cpy(buf + n, len - n, "}", 1); + n += cpy(buf + n, len - n, "}", 1); + pop_stringify(); + return n; } static size_t strnum(jsval_t value, char *buf, size_t len) { @@ -879,6 +911,8 @@ const char *js_str(struct js *js, jsval_t value) { if (is_err(value)) return js->errmsg; if (js->brk + sizeof(jsoff_t) >= js->size) return ""; + // Reset stringify depth for each new stringification + stringify_depth = 0; len = tostr(js, value, buf, available); js_mkstr(js, NULL, len); return buf; diff --git a/tests/test_circular.js b/tests/test_circular.js new file mode 100644 index 0000000..ef7a88c --- /dev/null +++ b/tests/test_circular.js @@ -0,0 +1,11 @@ +const obj = {}; +obj.self = obj; +console.log('Simple circular:', obj); +const a = { name: 'a' }; +const b = { name: 'b' }; +a.b = b; +b.a = a; +console.log('Mutual circular:', a); +const arr = [1, 2, 3]; +arr.push(arr); +console.log('Array circular:', arr); diff --git a/tests/window.cjs b/tests/window.cjs new file mode 100644 index 0000000..8e47a4c --- /dev/null +++ b/tests/window.cjs @@ -0,0 +1,2 @@ +const window = this; +console.log(window);