Explorar o código

LibJS: Normalize NaN values in Sets and Maps

This ensures that different NaN types (e.g. 0/0, 0 * Infinity, etc) are
mapped to the same Set/Map entry.
Idan Horowitz %!s(int64=3) %!d(string=hai) anos
pai
achega
59080f441e

+ 8 - 0
Userland/Libraries/LibJS/Runtime/ValueTraits.h

@@ -1,6 +1,7 @@
 /*
 /*
  * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2020-2022, Idan Horowitz <idan.horowitz@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -23,6 +24,13 @@ struct ValueTraits : public Traits<Value> {
 
 
         if (value.is_negative_zero())
         if (value.is_negative_zero())
             value = Value(0);
             value = Value(0);
+        // In the IEEE 754 standard a NaN value is encoded as any value from 0x7ff0000000000001 to 0x7fffffffffffffff,
+        // with the least significant bits (referred to as the 'payload') carrying some kind of diagnostic information
+        // indicating the source of the NaN. Since ECMA262 does not differentiate between different kinds of NaN values,
+        // Sets and Maps must not differentiate between them either.
+        // This is achieved by replacing any NaN value by a canonical qNaN.
+        else if (value.is_nan())
+            value = js_nan();
 
 
         return u64_hash(value.encoded()); // FIXME: Is this the best way to hash pointers, doubles & ints?
         return u64_hash(value.encoded()); // FIXME: Is this the best way to hash pointers, doubles & ints?
     }
     }

+ 9 - 0
Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.get.js

@@ -9,3 +9,12 @@ test("basic functionality", () => {
     expect(map.get("a")).toBe(0);
     expect(map.get("a")).toBe(0);
     expect(map.get("d")).toBe(undefined);
     expect(map.get("d")).toBe(undefined);
 });
 });
+
+test("NaN differentiation", () => {
+    const map = new Map();
+    map.set(NaN, "a");
+
+    expect(map.get(0 / 0)).toBe("a");
+    expect(map.get(0 * Infinity)).toBe("a");
+    expect(map.get(Infinity - Infinity)).toBe("a");
+});

+ 9 - 0
Userland/Libraries/LibJS/Tests/builtins/Set/Set.prototype.has.js

@@ -11,3 +11,12 @@ test("basic functionality", () => {
     expect(set.has(1)).toBeTrue();
     expect(set.has(1)).toBeTrue();
     expect(set.has("serenity")).toBeFalse();
     expect(set.has("serenity")).toBeFalse();
 });
 });
+
+test("NaN differentiation", () => {
+    const set = new Set();
+    set.add(NaN);
+
+    expect(set.has(0 / 0)).toBeTrue();
+    expect(set.has(0 * Infinity)).toBeTrue();
+    expect(set.has(Infinity - Infinity)).toBeTrue();
+});