summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2024-03-13 21:05:04 +0100
committerJo-Philipp Wich <jo@mein.io>2024-03-13 23:08:37 +0100
commitbe767ae197babd656d4f5d9c2d5013e39ddbe656 (patch)
tree37b790dae8f45a02da5b5cbe7266875e9bc1f540
parentba3855ae3775197f3594fc2615cac539075bd2fb (diff)
vm: rework `in` operator semantics
- Ensure that testing for array membership does strict equality tests - Ensure that `(NaN in [ NaN ]) == true` - Do not perform implicit value conversion when testing for object keys, to avoid nonsensical results such as `([] in { "[ ]": true }) == true` - Add test cases for the `in` operator Fixes: #193 Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--tests/custom/00_syntax/28_in_operator129
-rw-r--r--vm.c49
2 files changed, 163 insertions, 15 deletions
diff --git a/tests/custom/00_syntax/28_in_operator b/tests/custom/00_syntax/28_in_operator
new file mode 100644
index 0000000..7d58a43
--- /dev/null
+++ b/tests/custom/00_syntax/28_in_operator
@@ -0,0 +1,129 @@
+The "in" operator allows testing whether a given value is an item of
+a specified array or whether a given key is present in a specified
+dictionary.
+
+
+1. The `in` operator returns true if the given element is an item of
+the specified array. Strict equality tests are performed.
+
+-- Expect stdout --
+[
+ true,
+ false,
+ true,
+ false,
+ true,
+ false,
+ true,
+ false
+]
+-- End --
+
+-- Testcase --
+{%
+ let o = {};
+ let a = [ o, {}, "", null, false ];
+
+ printf("%.J\n", [
+ o in a,
+ {} in a,
+ "" in a,
+ "test" in a,
+ null in a,
+ [] in a,
+ false in a,
+ true in a
+ ]);
+%}
+-- End --
+
+2. Strict equality when testing array membership should rule out implict
+type coercion.
+
+-- Expect stdout --
+[
+ true,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false
+]
+-- End --
+
+-- Testcase --
+{%
+ let a = [ "", true ];
+
+ printf("%.J\n", [
+ "" in a,
+ 0 in a,
+ false in a,
+ null in a,
+ true in a,
+ 1 in a,
+ 1.0 in a
+ ]);
+%}
+-- End --
+
+3. While there is the rule that `(NaN === NaN) == false`, testing for NaN
+in a given array containing NaN should yield `true`.
+
+-- Expect stdout --
+[
+ true
+]
+-- End --
+
+-- Testcase --
+{%
+ let a = [ NaN ];
+
+ printf("%.J\n", [
+ NaN in a
+ ]);
+%}
+-- End --
+
+4. When the `in` operator is applied to an object, it tests whether the given
+string value is a key of the specified object.
+
+-- Expect stdout --
+[
+ true,
+ true,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false
+]
+-- End --
+
+-- Testcase --
+{%
+ let o = {
+ "1": true,
+ "test": false,
+ "empty": null,
+ "false": 0,
+ "true": 1,
+ "[ ]": "array",
+ "{ }": "object"
+ };
+
+ printf("%.J\n", [
+ "1" in o,
+ "test" in o,
+ "empty" in o,
+ 1 in o, // not implicitly converted to "1"
+ false in o, // not implicitly converted to "false"
+ true in o, // not implicitly converted to "true"
+ [] in o, // not implicitly converted to "[ ]"
+ {} in o // not implicitly converted to "{ }"
+ ]);
+%}
+-- End --
diff --git a/vm.c b/vm.c
index 4642380..bb6dc2f 100644
--- a/vm.c
+++ b/vm.c
@@ -993,6 +993,37 @@ uc_vm_raise_exception(uc_vm_t *vm, uc_exception_type_t type, const char *fmt, ..
vm->exception.stacktrace = uc_vm_get_error_context(vm);
}
+static bool
+uc_vm_test_strict_equality(uc_value_t *v1, uc_value_t *v2, bool nan_equal)
+{
+ uc_type_t t1 = ucv_type(v1);
+ uc_type_t t2 = ucv_type(v2);
+ double d1, d2;
+
+ if (t1 != t2)
+ return false;
+
+ switch (t1) {
+ case UC_DOUBLE:
+ d1 = ((uc_double_t *)v1)->dbl;
+ d2 = ((uc_double_t *)v2)->dbl;
+
+ if (isnan(d1) && isnan(d2))
+ return nan_equal;
+
+ return (d1 == d2);
+
+ case UC_NULL:
+ case UC_BOOLEAN:
+ case UC_INTEGER:
+ case UC_STRING:
+ return ucv_is_equal(v1, v2);
+
+ default:
+ return (v1 == v2);
+ }
+}
+
static void
uc_vm_insn_load(uc_vm_t *vm, uc_vm_insn_t insn)
@@ -2066,7 +2097,6 @@ uc_vm_insn_in(uc_vm_t *vm, uc_vm_insn_t insn)
uc_value_t *item;
size_t arrlen, arridx;
bool found = false;
- char *key;
switch (ucv_type(r2)) {
case UC_ARRAY:
@@ -2074,7 +2104,7 @@ uc_vm_insn_in(uc_vm_t *vm, uc_vm_insn_t insn)
arridx < arrlen; arridx++) {
item = ucv_array_get(r2, arridx);
- if (ucv_compare(I_EQ, r1, item, NULL)) {
+ if (uc_vm_test_strict_equality(r1, item, true)) {
found = true;
break;
}
@@ -2083,14 +2113,8 @@ uc_vm_insn_in(uc_vm_t *vm, uc_vm_insn_t insn)
break;
case UC_OBJECT:
- if (ucv_type(r1) == UC_STRING) {
+ if (ucv_type(r1) == UC_STRING)
ucv_object_get(r2, ucv_string_get(r1), &found);
- }
- else {
- key = ucv_to_string(vm, r1);
- ucv_object_get(r2, key, &found);
- free(key);
- }
break;
@@ -2109,12 +2133,7 @@ uc_vm_insn_equality(uc_vm_t *vm, uc_vm_insn_t insn)
{
uc_value_t *r2 = uc_vm_stack_pop(vm);
uc_value_t *r1 = uc_vm_stack_pop(vm);
- bool equal;
-
- if (ucv_is_scalar(r1) && ucv_is_scalar(r2))
- equal = ucv_is_equal(r1, r2);
- else
- equal = (r1 == r2);
+ bool equal = uc_vm_test_strict_equality(r1, r2, false);
ucv_put(r1);
ucv_put(r2);