Skip to content

Commit

Permalink
vm: rework in operator semantics
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
jow- committed Mar 13, 2024
1 parent ba3855a commit 125d23c
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 15 deletions.
132 changes: 132 additions & 0 deletions tests/custom/00_syntax/28_in_operator
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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.

When testing whether an element is part of an array, strict equality
checking is performed.


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 --
49 changes: 34 additions & 15 deletions vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -2066,15 +2097,14 @@ 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:
for (arridx = 0, arrlen = ucv_array_length(r2);
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;
}
Expand All @@ -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;

Expand All @@ -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);
Expand Down

0 comments on commit 125d23c

Please sign in to comment.