diff --git a/examples/results.txt b/examples/results.txt index 0fedeff..71e81ed 100644 --- a/examples/results.txt +++ b/examples/results.txt @@ -1191,20 +1191,20 @@ compat-table/es6/well-known.toStringTag.js: OK compat-table/es6/well-known.toStringTag.misc-builtins.js: OK compat-table/es6/well-known.toStringTag.new-builtins.js: failed compat-table/es6/well-known.unscopables.js: OK -compat-table/es2016/Array.prototype.includes.generic.js: failed +compat-table/es2016/Array.prototype.includes.generic.js: OK compat-table/es2016/Array.prototype.includes.js: OK -compat-table/es2016/Array.prototype.includes.sparse.js: failed -compat-table/es2016/Array.prototype.includes.typed-array.js: TypeError: undefined is not a function +compat-table/es2016/Array.prototype.includes.sparse.js: OK +compat-table/es2016/Array.prototype.includes.typed-array.js: OK compat-table/es2016/exponentiation.assignment.js: OK compat-table/es2016/exponentiation.basic.js: OK compat-table/es2016/exponentiation.unary-negation-error.js: OK -compat-table/es2016/misc.Proxy-Array-includes.js: failed +compat-table/es2016/misc.Proxy-Array-includes.js: OK compat-table/es2016/misc.Proxy-enumerate-removed.js: OK compat-table/es2016/misc.generator-no-new.js: OK compat-table/es2016/misc.generator-throw-inner.js: OK compat-table/es2016/misc.nested-rest-destructuring-decl.js: OK compat-table/es2016/misc.nested-rest-destructuring-params.js: OK -compat-table/es2016/misc.strict-fn-non-simple-params-error.js: failed +compat-table/es2016/misc.strict-fn-non-simple-params-error.js: OK compat-table/es2017/Atomics.add.js: OK compat-table/es2017/Atomics.and.js: OK compat-table/es2017/Atomics.compareExchange.js: OK diff --git a/src/ant.c b/src/ant.c index 1436c41..1ec262c 100644 --- a/src/ant.c +++ b/src/ant.c @@ -8340,35 +8340,101 @@ static ant_value_t builtin_array_join(ant_t *js, ant_value_t *args, int nargs) { free(result); return ret; } -static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { - ant_value_t arr = js->this_val; - - if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) - return js_mkerr(js, "includes called on non-array"); - - if (nargs == 0) return mkval(T_BOOL, 0); - ant_value_t search = args[0]; +static inline bool array_includes_same_value_zero(ant_t *js, ant_value_t val, ant_value_t search) { + return ( + vtype(val) == T_NUM && + vtype(search) == T_NUM && + isnan(tod(val)) && + isnan(tod(search)) + ) || strict_eq_values(js, val, search); +} + +static inline ant_offset_t array_includes_length_from_number(double len_num) { + if (isnan(len_num) || len_num <= 0) return 0; + if (len_num >= (double)UINT32_MAX) return UINT32_MAX; + return (ant_offset_t)len_num; +} + +static inline ant_offset_t array_includes_start_index(ant_t *js, ant_value_t *args, int nargs, ant_offset_t len) { + int64_t start = 0; + if (nargs >= 2 && vtype(args[1]) != T_UNDEF) { + double from_index_num = js_to_number(js, args[1]); + if (!isnan(from_index_num)) start = (int64_t)from_index_num; + if (start < 0) { + start += (int64_t)len; + if (start < 0) start = 0; + }} + + if ((uint64_t)start >= (uint64_t)len) return len; + return (ant_offset_t)start; +} + +static ant_value_t array_includes_dense_fast( + ant_t *js, ant_value_t arr, ant_value_t search, ant_value_t *args, int nargs +) { + if (!array_obj_ptr(arr) || is_proxy(arr)) return js_mkundef(); + ant_offset_t len = get_array_length(js, arr); if (len == 0) return mkval(T_BOOL, 0); - - ant_offset_t start = 0; - if (nargs >= 2 && vtype(args[1]) == T_NUM) { - int s = (int) tod(args[1]); - if (s < 0) s = (int)len + s; - if (s < 0) s = 0; - start = (ant_offset_t) s; + + ant_offset_t doff = get_dense_buf(arr); + if (!doff) return js_mkundef(); + + ant_offset_t dense_len = dense_iterable_length(js, arr); + if (dense_len != len) return js_mkundef(); + + ant_offset_t start = array_includes_start_index(js, args, nargs, len); + if (start >= len) return mkval(T_BOOL, 0); + + for (ant_offset_t i = start; i < len; i++) { + ant_value_t val = dense_get(doff, i); + if (is_empty_slot(val)) return js_mkundef(); + if (array_includes_same_value_zero(js, val, search)) return mkval(T_BOOL, 1); } + + return mkval(T_BOOL, 0); +} + +static ant_value_t array_includes_generic( + ant_t *js, ant_value_t arr, ant_value_t search, ant_value_t *args, int nargs +) { + ant_value_t len_val = js_getprop_fallback(js, arr, "length"); + if (is_err(len_val)) return len_val; + + ant_offset_t len = array_includes_length_from_number(js_to_number(js, len_val)); + if (len == 0) return mkval(T_BOOL, 0); + ant_offset_t start = array_includes_start_index(js, args, nargs, len); + if (start >= len) return mkval(T_BOOL, 0); + for (ant_offset_t i = start; i < len; i++) { - ant_value_t val = arr_get(js, arr, i); - if (vtype(val) == T_NUM && vtype(search) == T_NUM && isnan(tod(val)) && isnan(tod(search))) return mkval(T_BOOL, 1); - if (strict_eq_values(js, val, search)) return mkval(T_BOOL, 1); + char idxstr[24]; + size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i); + idxstr[idxlen] = '\0'; + + ant_value_t val = js_getprop_fallback(js, arr, idxstr); + if (is_err(val)) return val; + if (array_includes_same_value_zero(js, val, search)) return mkval(T_BOOL, 1); } return mkval(T_BOOL, 0); } +static ant_value_t builtin_array_includes(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t arr = js->this_val; + + if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) + return js_mkerr(js, "includes called on non-array"); + + ant_value_t search = (nargs > 0) ? args[0] : js_mkundef(); + + ant_value_t fast = array_includes_dense_fast(js, arr, search, args, nargs); + if (vtype(fast) != T_UNDEF) return fast; + + return array_includes_generic(js, arr, search, args, nargs); +} + static ant_value_t builtin_array_every(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; diff --git a/src/modules/buffer.c b/src/modules/buffer.c index e8efbfb..ec4d8e5 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -2230,6 +2230,69 @@ static ant_value_t js_typedarray_indexOf(ant_t *js, ant_value_t *args, int nargs return js_mknum(-1); } +static ant_value_t js_typedarray_includes(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this_val = js_getthis(js); + TypedArrayData *ta_data = buffer_get_typedarray_data(this_val); + + if (!ta_data || !ta_data->buffer || ta_data->buffer->is_detached) { + return js_mkerr(js, "Invalid TypedArray"); + } + + size_t len = ta_data->length; + ant_value_t search = (nargs > 0) ? args[0] : js_mkundef(); + + if (len == 0) return js_false; + int64_t from_index = 0; + + if (nargs > 1 && vtype(args[1]) != T_UNDEF) { + double from_index_num = js_to_number(js, args[1]); + if (!isnan(from_index_num)) from_index = (int64_t)from_index_num; + if (from_index < 0) { + from_index += (int64_t)len; + if (from_index < 0) from_index = 0; + }} + + if ((size_t)from_index >= len) return js_false; + + if (ta_data->type == TYPED_ARRAY_BIGINT64) { + int64_t needle = 0; + if (vtype(search) == T_BIGINT) { + if (!bigint_to_int64_wrapping(js, search, &needle)) return js_false; + } else needle = (int64_t)js_to_number(js, search); + + int64_t *data = (int64_t *)(ta_data->buffer->data + ta_data->byte_offset); + for (size_t i = (size_t)from_index; i < len; i++) { + if (data[i] == needle) return js_true; + } + + return js_false; + } + + if (ta_data->type == TYPED_ARRAY_BIGUINT64) { + uint64_t needle = 0; + if (vtype(search) == T_BIGINT) { + if (!bigint_to_uint64_wrapping(js, search, &needle)) return js_false; + } else needle = (uint64_t)js_to_number(js, search); + + uint64_t *data = (uint64_t *)(ta_data->buffer->data + ta_data->byte_offset); + for (size_t i = (size_t)from_index; i < len; i++) { + if (data[i] == needle) return js_true; + } + + return js_false; + } + + double needle = js_to_number(js, search); + for (size_t i = (size_t)from_index; i < len; i++) { + double value = 0; + if (!typedarray_read_number(ta_data, i, &value)) return js_false; + if (isnan(value) && isnan(needle)) return js_true; + if (value == needle) return js_true; + } + + return js_false; +} + // Buffer.prototype.toString(encoding) static ant_value_t js_buffer_slice(ant_t *js, ant_value_t *args, int nargs) { return js_typedarray_subarray(js, args, nargs); @@ -2698,6 +2761,7 @@ void init_buffer_module() { js_set(js, typedarray_proto, "toString", js_mkfun(js_typedarray_toString)); js_set(js, typedarray_proto, "join", js_mkfun(js_typedarray_join)); js_set(js, typedarray_proto, "indexOf", js_mkfun(js_typedarray_indexOf)); + js_set(js, typedarray_proto, "includes", js_mkfun(js_typedarray_includes)); js_set_sym(js, typedarray_proto, get_toStringTag_sym(), js_mkstr(js, "TypedArray", 10)); g_typedarray_iter_proto = js_mkobj(js); @@ -2710,6 +2774,7 @@ void init_buffer_module() { js_set(js, typedarray_proto, "entries", js_mkfun(ta_entries)); js_set_sym(js, typedarray_proto, get_iterator_sym(), js_get(js, typedarray_proto, "values")); + // TODO: find a better way of doing this, macro is code smell #define SETUP_TYPEDARRAY(name) \ do { \ ant_value_t name##_ctor_obj = js_mkobj(js); \ diff --git a/src/silver/compiler.c b/src/silver/compiler.c index aa8507b..eba793e 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -4303,6 +4303,13 @@ sv_func_t *compile_function_body( sv_ast_t *p = node->args.items[i]; if (p->type != N_IDENT) { has_non_simple_params = true; break; } } + + if (comp.is_strict && has_non_simple_params) { + js_mkerr_typed( + comp.js, JS_ERR_SYNTAX, + "Illegal 'use strict' directive in function with non-simple parameter list"); + return NULL; + } bool repl_top = is_repl_top_level(&comp); if (!has_non_simple_params && node->body) { diff --git a/tests/test_es2016_includes_and_strict.cjs b/tests/test_es2016_includes_and_strict.cjs new file mode 100644 index 0000000..bcae16a --- /dev/null +++ b/tests/test_es2016_includes_and_strict.cjs @@ -0,0 +1,76 @@ +const assert = require("node:assert"); + +let passed = 0; +assert.strictEqual([].includes.call({ + get "0"() { + passed = NaN; + return "foo"; + }, + get "11"() { + passed += 1; + return 0; + }, + get "19"() { + passed += 1; + return "foo"; + }, + get "21"() { + passed = NaN; + return "foo"; + }, + get length() { + passed += 1; + return 24; + } +}, "foo", 6), true); +assert.strictEqual(passed, 3); + +assert.strictEqual([,].includes(), true); +assert.strictEqual(Array(1).includes(), true); + +let getLog = []; +let proxy = new Proxy({ length: 3, 0: "", 1: "", 2: "", 3: "" }, { + get(target, key) { + getLog.push(String(key)); + return target[key]; + } +}); +Array.prototype.includes.call(proxy, {}); +assert.deepStrictEqual(getLog, ["length", "0", "1", "2"]); + +getLog = []; +proxy = new Proxy({ length: 4, 0: NaN, 1: "", 2: NaN, 3: "" }, { + get(target, key) { + getLog.push(String(key)); + return target[key]; + } +}); +Array.prototype.includes.call(proxy, NaN, 1); +assert.deepStrictEqual(getLog, ["length", "1", "2"]); + +[ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +].forEach((TypedArray) => { + const view = new TypedArray([1, 2, 3]); + assert.strictEqual(view.includes(1), true); + assert.strictEqual(view.includes(4), false); + assert.strictEqual(view.includes(1, 1), false); +}); + +let threw = false; +try { + Function("function bar(...a) {'use strict';}")(); +} catch (_err) { + threw = true; +} +assert.strictEqual(threw, true); + +console.log("ES2016 includes and strict-mode regressions pass");