diff --git a/meson.build b/meson.build index b89b8c6..f09c3dc 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('ant', 'c', default_options: [ 'optimization=2', - 'c_std=c23', + 'c_std=c11', 'default_library=static' ]) @@ -40,6 +40,7 @@ tlsuv_opts.append_compile_args('c', [ tlsuv_opts.add_cmake_defines({'BUILD_TESTING': 'OFF'}) tlsuv_dep = cmake.subproject('tlsuv', options: tlsuv_opts).dependency('tlsuv') +bdwgc_dep = subproject('bdwgc').get_variable('bdwgc_dep') uthash_dep = subproject('uthash').get_variable('uthash_dep') yyjson_dep = subproject('yyjson').get_variable('yyjson_dep') uuidv7_dep = subproject('uuidv7').get_variable('uuidv7_dep') @@ -67,7 +68,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.40') +version_conf.set('ANT_VERSION', '0.0.6.41') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) @@ -104,6 +105,7 @@ snapshot_h = custom_target( ) ant_deps = [ + bdwgc_dep, llhttp, mongoose_dep, libuv_dep, argtable3_dep, tlsuv_dep, libsodium_dep, diff --git a/src/ant.c b/src/ant.c index e00d5f6..32b2988 100644 --- a/src/ant.c +++ b/src/ant.c @@ -202,8 +202,8 @@ static const char *typestr(uint8_t t) { static jsval_t tov(double d) { union { double d; jsval_t v; } u = {d}; return u.v; } static double tod(jsval_t v) { union { jsval_t v; double d; } u = {v}; return u.d; } -static jsval_t mkval(uint8_t type, uint64_t data) { return ((jsval_t) 0x7ff0U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL); } -static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7ffU; } +static jsval_t mkval(uint8_t type, uint64_t data) { return ((jsval_t) 0x7fe0U << 48U) | ((jsval_t) (type) << 48) | (data & 0xffffffffffffUL); } +static bool is_nan(jsval_t v) { return (v >> 52U) == 0x7feU; } static uint8_t vtype(jsval_t v) { return is_nan(v) ? ((v >> 48U) & 15U) : (uint8_t) T_NUM; } static size_t vdata(jsval_t v) { return (size_t) (v & ~((jsval_t) 0x7fffUL << 48U)); } static jsval_t mkcoderef(jsval_t off, jsoff_t len) { return mkval(T_CODEREF, (off & 0xffffffU) | ((jsval_t)(len & 0xffffffU) << 24U)); } @@ -742,6 +742,9 @@ static size_t strnum(jsval_t value, char *buf, size_t len) { double dv = tod(value), iv; double frac = modf(dv, &iv); + if (isnan(dv)) return cpy(buf, len, "NaN", 3); + if (isinf(dv)) return cpy(buf, len, dv > 0 ? "Infinity" : "-Infinity", dv > 0 ? 8 : 9); + if (dv >= -9007199254740991.0 && dv <= 9007199254740991.0) { if (frac == 0.0) { return (size_t) snprintf(buf, len, "%.0f", dv); @@ -2242,12 +2245,53 @@ static jsval_t do_op(struct js *js, uint8_t op, jsval_t lhs, jsval_t rhs) { return do_string_op(js, op, l_str, r_str); } if (vtype(l) == T_STR && vtype(r) == T_STR) return do_string_op(js, op, l, r); - if (is_unary(op) && vtype(r) != T_NUM) return js_mkerr(js, "type mismatch"); - if (!is_unary(op) && op != TOK_DOT && op != TOK_OPTIONAL_CHAIN && op != TOK_INSTANCEOF && (vtype(l) != T_NUM || vtype(r) != T_NUM)) return js_mkerr(js, "type mismatch"); - double a = tod(l), b = tod(r); + double a = 0.0, b = 0.0; + + if (vtype(l) == T_NUM) { + a = tod(l); + } else if (vtype(l) == T_STR) { + jsoff_t slen, off = vstr(js, l, &slen); + char *endptr; + char temp[256]; + size_t copy_len = slen < sizeof(temp) - 1 ? slen : sizeof(temp) - 1; + memcpy(temp, &js->mem[off], copy_len); + temp[copy_len] = '\0'; + a = strtod(temp, &endptr); + if (endptr == temp || *endptr != '\0') a = NAN; + } else if (vtype(l) == T_BOOL) { + a = vdata(l) ? 1.0 : 0.0; + } else if (vtype(l) == T_NULL) { + a = 0.0; + } else if (vtype(l) == T_UNDEF) { + a = NAN; + } else { + a = NAN; + } + + if (vtype(r) == T_NUM) { + b = tod(r); + } else if (vtype(r) == T_STR) { + jsoff_t slen, off = vstr(js, r, &slen); + char *endptr; + char temp[256]; + size_t copy_len = slen < sizeof(temp) - 1 ? slen : sizeof(temp) - 1; + memcpy(temp, &js->mem[off], copy_len); + temp[copy_len] = '\0'; + b = strtod(temp, &endptr); + if (endptr == temp || *endptr != '\0') b = NAN; + } else if (vtype(r) == T_BOOL) { + b = vdata(r) ? 1.0 : 0.0; + } else if (vtype(r) == T_NULL) { + b = 0.0; + } else if (vtype(r) == T_UNDEF) { + b = NAN; + } else { + b = NAN; + } + switch (op) { - case TOK_DIV: return tod(r) == 0 ? js_mkerr(js, "div by zero") : tov(a / b); + case TOK_DIV: return tov(a / b); case TOK_REM: return tov(a - b * ((double) (long) (a / b))); case TOK_MUL: return tov(a * b); case TOK_PLUS: return tov(a + b); @@ -5017,6 +5061,26 @@ static jsval_t builtin_String(struct js *js, jsval_t *args, int nargs) { return js_mkstr(js, str, strlen(str)); } +static jsval_t builtin_Number_isNaN(struct js *js, jsval_t *args, int nargs) { + if (nargs == 0) return mkval(T_BOOL, 0); + jsval_t arg = args[0]; + + if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0); + + double val = tod(arg); + return mkval(T_BOOL, isnan(val) ? 1 : 0); +} + +static jsval_t builtin_Number_isFinite(struct js *js, jsval_t *args, int nargs) { + if (nargs == 0) return mkval(T_BOOL, 0); + jsval_t arg = args[0]; + + if (vtype(arg) != T_NUM) return mkval(T_BOOL, 0); + + double val = tod(arg); + return mkval(T_BOOL, isfinite(val) ? 1 : 0); +} + static jsval_t builtin_Number(struct js *js, jsval_t *args, int nargs) { if (nargs == 0) return tov(0.0); jsval_t arg = args[0]; @@ -5473,10 +5537,16 @@ static jsval_t builtin_array_join(struct js *js, jsval_t *args, int nargs) { const char *sep = ","; jsoff_t sep_len = 1; - if (nargs >= 1 && vtype(args[0]) == T_STR) { - sep_len = 0; - jsoff_t sep_off = vstr(js, args[0], &sep_len); - sep = (const char *) &js->mem[sep_off]; + if (nargs >= 1) { + if (vtype(args[0]) == T_STR) { + sep_len = 0; + jsoff_t sep_off = vstr(js, args[0], &sep_len); + sep = (const char *) &js->mem[sep_off]; + } else if (vtype(args[0]) != T_UNDEF) { + const char *sep_str = js_str(js, args[0]); + sep = sep_str; + sep_len = (jsoff_t) strlen(sep_str); + } } jsoff_t off = lkp(js, arr, "length", 6); @@ -6012,15 +6082,15 @@ static jsval_t builtin_string_template(struct js *js, jsval_t *args, int nargs) static jsval_t builtin_string_charCodeAt(struct js *js, jsval_t *args, int nargs) { jsval_t str = js->this_val; if (vtype(str) != T_STR) return js_mkerr(js, "charCodeAt called on non-string"); - if (nargs < 1 || vtype(args[0]) != T_NUM) return tov(NAN); + if (nargs < 1 || vtype(args[0]) != T_NUM) return tov(-NAN); double idx_d = tod(args[0]); - if (idx_d < 0 || idx_d != (double)(long)idx_d) return tov(NAN); + if (idx_d < 0 || idx_d != (double)(long)idx_d) return tov(-NAN); jsoff_t idx = (jsoff_t) idx_d; jsoff_t str_len = offtolen(loadoff(js, (jsoff_t) vdata(str))); - if (idx >= str_len) return tov(NAN); + if (idx >= str_len) return tov(-NAN); jsoff_t str_off = (jsoff_t) vdata(str) + sizeof(jsoff_t); unsigned char ch = (unsigned char) js->mem[str_off + idx]; @@ -6270,7 +6340,7 @@ static jsval_t builtin_number_toExponential(struct js *js, jsval_t *args, int na } static jsval_t builtin_parseInt(struct js *js, jsval_t *args, int nargs) { - if (nargs < 1) return tov(NAN); + if (nargs < 1) return tov(-NAN); jsval_t str_val = args[0]; if (vtype(str_val) != T_STR) { @@ -6284,13 +6354,13 @@ static jsval_t builtin_parseInt(struct js *js, jsval_t *args, int nargs) { int radix = 10; if (nargs >= 2 && vtype(args[1]) == T_NUM) { radix = (int) tod(args[1]); - if (radix < 2 || radix > 36) return tov(NAN); + if (radix < 2 || radix > 36) return tov(-NAN); } jsoff_t i = 0; while (i < str_len && is_space(str[i])) i++; - if (i >= str_len) return tov(NAN); + if (i >= str_len) return tov(-NAN); int sign = 1; if (str[i] == '-') { @@ -6326,13 +6396,13 @@ static jsval_t builtin_parseInt(struct js *js, jsval_t *args, int nargs) { i++; } - if (!found_digit) return tov(NAN); + if (!found_digit) return tov(-NAN); return tov(sign * result); } static jsval_t builtin_parseFloat(struct js *js, jsval_t *args, int nargs) { - if (nargs < 1) return tov(NAN); + if (nargs < 1) return tov(-NAN); jsval_t str_val = args[0]; if (vtype(str_val) != T_STR) { @@ -6346,12 +6416,12 @@ static jsval_t builtin_parseFloat(struct js *js, jsval_t *args, int nargs) { jsoff_t i = 0; while (i < str_len && is_space(str[i])) i++; - if (i >= str_len) return tov(NAN); + if (i >= str_len) return tov(-NAN); char *end; double result = strtod(&str[i], &end); - if (end == &str[i]) return tov(NAN); + if (end == &str[i]) return tov(-NAN); return tov(result); } @@ -7715,7 +7785,13 @@ struct js *js_create(void *buf, size_t len) { setprop(js, glob, js_mkstr(js, "eval", 4), js_mkfun(builtin_eval)); setprop(js, glob, js_mkstr(js, "Function", 8), js_mkfun(builtin_Function)); setprop(js, glob, js_mkstr(js, "String", 6), js_mkfun(builtin_String)); - setprop(js, glob, js_mkstr(js, "Number", 6), js_mkfun(builtin_Number)); + + jsval_t number_ctor_obj = mkobj(js, 0); + setprop(js, number_ctor_obj, js_mkstr(js, "__native_func", 13), js_mkfun(builtin_Number)); + setprop(js, number_ctor_obj, js_mkstr(js, "isNaN", 5), js_mkfun(builtin_Number_isNaN)); + setprop(js, number_ctor_obj, js_mkstr(js, "isFinite", 8), js_mkfun(builtin_Number_isFinite)); + setprop(js, glob, js_mkstr(js, "Number", 6), mkval(T_FUNC, vdata(number_ctor_obj))); + setprop(js, glob, js_mkstr(js, "Boolean", 7), js_mkfun(builtin_Boolean)); setprop(js, glob, js_mkstr(js, "Array", 5), js_mkfun(builtin_Array)); setprop(js, glob, js_mkstr(js, "Error", 5), js_mkfun(builtin_Error)); @@ -7723,6 +7799,9 @@ struct js *js_create(void *buf, size_t len) { setprop(js, glob, js_mkstr(js, "parseInt", 8), js_mkfun(builtin_parseInt)); setprop(js, glob, js_mkstr(js, "parseFloat", 10), js_mkfun(builtin_parseFloat)); + setprop(js, glob, js_mkstr(js, "NaN", 3), tov(NAN)); + setprop(js, glob, js_mkstr(js, "Infinity", 8), tov(INFINITY)); + jsval_t date_ctor_obj = mkobj(js, 0); setprop(js, date_ctor_obj, js_mkstr(js, "__native_func", 13), js_mkfun(builtin_Date)); setprop(js, date_ctor_obj, js_mkstr(js, "now", 3), js_mkfun(builtin_Date_now)); diff --git a/src/modules/fetch.c b/src/modules/fetch.c index 28096f2..74fad97 100644 --- a/src/modules/fetch.c +++ b/src/modules/fetch.c @@ -68,7 +68,7 @@ static jsval_t create_response(struct js *js, int status, const char *body, size js_set(js, json_obj, "__code", json_str); jsval_t json_func = js_mknum(0); memcpy(&json_func, &json_obj, sizeof(jsval_t)); - json_func = (json_func & 0xFFFFFFFFFFFFULL) | (0x7FF0000000000000ULL | ((uint64_t)7 << 48)); + json_func = (json_func & 0xFFFFFFFFFFFFULL) | (0x7FE0000000000000ULL | ((uint64_t)7 << 48)); js_set(js, response_obj, "json", json_func); return response_obj; @@ -304,7 +304,7 @@ static jsval_t js_fetch(struct js *js, jsval_t *args, int nargs) { jsval_t wrapper_func = js_mknum(0); memcpy(&wrapper_func, &wrapper_obj, sizeof(jsval_t)); - wrapper_func = (wrapper_func & 0xFFFFFFFFFFFFULL) | (0x7FF0000000000000ULL | ((uint64_t)7 << 48)); + wrapper_func = (wrapper_func & 0xFFFFFFFFFFFFULL) | (0x7FE0000000000000ULL | ((uint64_t)7 << 48)); queue_microtask(js, wrapper_func); return promise; diff --git a/subprojects/bdwgc.wrap b/subprojects/bdwgc.wrap new file mode 100644 index 0000000..f1c2eb9 --- /dev/null +++ b/subprojects/bdwgc.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = gc-8.2.10 +source_url = https://github.com/bdwgc/bdwgc/releases/download/v8.2.10/gc-8.2.10.tar.gz +source_filename = gc-8.2.10.tar.gz +source_hash = 832cf4f7cf676b59582ed3b1bbd90a8d0e0ddbc3b11cb3b2096c5177ce39cc47 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/bdwgc_8.2.10-1/gc-8.2.10.tar.gz +patch_filename = bdwgc_8.2.10-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/bdwgc_8.2.10-1/get_patch +patch_hash = 6e789989cb6c96738bb896f6167c1b6cb976a4734fc191e7fd84896a2966ab97 +wrapdb_version = 8.2.10-1 + +[provide] +dependency_names = bdw-gc diff --git a/test_minimal.cjs b/test_minimal.cjs new file mode 100644 index 0000000..b34c955 --- /dev/null +++ b/test_minimal.cjs @@ -0,0 +1,22 @@ +console.log('Testing recursive string assignment with objects'); + +function testRecursion(depth, params) { + if (depth >= 10) return 'done'; + + let paramValue = ''; + // This assignment causes parser corruption in certain contexts + paramValue = 'test'; + + if (paramValue !== '') { + params['test' + depth] = paramValue; + const result = testRecursion(depth + 1, params); + delete params['test' + depth]; + return result; + } + + return 'failed'; +} + +const params = {}; +const result = testRecursion(0, params); +console.log('Result:', result); diff --git a/tests/test_nan_infinity.cjs b/tests/test_nan_infinity.cjs new file mode 100644 index 0000000..839c524 --- /dev/null +++ b/tests/test_nan_infinity.cjs @@ -0,0 +1,79 @@ +// Test NaN and Infinity values +console.log('=== NaN Tests ==='); + +// Basic NaN value +console.log('NaN:', NaN); +console.log('typeof NaN:', typeof NaN); + +// Type coercion resulting in NaN +console.log('"wat" - 1:', "wat" - 1); +console.log('"hello" * 2:', "hello" * 2); +console.log('undefined + 5:', undefined + 5); +console.log('10 / "abc":', 10 / "abc"); +console.log('0/0:', 0/0); + +// Valid string to number coercion +console.log('"5" - 2:', "5" - 2); +console.log('"10" * 2:', "10" * 2); + +// Boolean and null coercion +console.log('true + 1:', true + 1); +console.log('false + 1:', false + 1); +console.log('null + 5:', null + 5); + +// NaN properties +console.log('NaN === NaN:', NaN === NaN); // Should be false +console.log('NaN !== NaN:', NaN !== NaN); // Should be true + +// Number.isNaN tests +console.log('\n=== Number.isNaN Tests ==='); +console.log('Number.isNaN(NaN):', Number.isNaN(NaN)); +console.log('Number.isNaN("wat" - 1):', Number.isNaN("wat" - 1)); +console.log('Number.isNaN(123):', Number.isNaN(123)); +console.log('Number.isNaN("hello"):', Number.isNaN("hello")); // Should be false (not a number type) +console.log('Number.isNaN(undefined):', Number.isNaN(undefined)); // Should be false +console.log('Number.isNaN(null):', Number.isNaN(null)); // Should be false + +// parseInt/parseFloat with invalid input +console.log('\n=== parseInt/parseFloat NaN ==='); +console.log('parseInt("notanumber"):', parseInt("notanumber")); +console.log('parseInt("123abc"):', parseInt("123abc")); +console.log('parseFloat("xyz"):', parseFloat("xyz")); + +// Infinity tests +console.log('\n=== Infinity Tests ==='); +console.log('Infinity:', Infinity); +console.log('typeof Infinity:', typeof Infinity); +console.log('1/0:', 1/0); +console.log('-1/0:', -1/0); + +// Number.isFinite tests +console.log('\n=== Number.isFinite Tests ==='); +console.log('Number.isFinite(123):', Number.isFinite(123)); +console.log('Number.isFinite(1/0):', Number.isFinite(1/0)); +console.log('Number.isFinite(-1/0):', Number.isFinite(-1/0)); +console.log('Number.isFinite(NaN):', Number.isFinite(NaN)); +console.log('Number.isFinite(Infinity):', Number.isFinite(Infinity)); +console.log('Number.isFinite("123"):', Number.isFinite("123")); // Should be false (not a number type) + +// Array.join with NaN +console.log('\n=== Array.join with NaN ==='); +console.log('[1,2,3].join(NaN):', [1,2,3].join(NaN)); +console.log('[1,2,3].join(0/0):', [1,2,3].join(0/0)); +console.log('Array(16).join("wat" - 1) + " Batman!":', Array(16).join("wat" - 1) + " Batman!"); + +// String operations with NaN +console.log('\n=== String operations ==='); +console.log('"Result: " + NaN:', "Result: " + NaN); +console.log('"Value: " + (0/0):', "Value: " + (0/0)); + +// Arithmetic with Infinity +console.log('\n=== Arithmetic with Infinity ==='); +console.log('Infinity + 1:', Infinity + 1); +console.log('Infinity - 1:', Infinity - 1); +console.log('Infinity * 2:', Infinity * 2); +console.log('Infinity / 2:', Infinity / 2); +console.log('Infinity - Infinity:', Infinity - Infinity); +console.log('Infinity * 0:', Infinity * 0); + +console.log('\n=== All tests completed ===');