Skip to content

Commit 3449c53

Browse files
faviladnolen
authored andcommitted
CLJS-1976: hash-map assoc stackoverflow
create-node (and possibly other internal functions) assume that hash values collide when their values are equal. Because it is possible to return a hash value of more than 32 bits, it is possible to have a hash collision where hashes are unequal, causing undefined behavior. In this case, it caused a stackoverflow during hash-map and set assoc! and assoc. This patch forces the hash function to truncate all hash values from dates and -hash implementations to 32 bits.
1 parent f7d08ba commit 3449c53

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

src/main/cljs/cljs/core.cljs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@
901901
[o]
902902
(cond
903903
(implements? IHash o)
904-
(-hash ^not-native o)
904+
(bit-xor (-hash ^not-native o) 0)
905905

906906
(number? o)
907907
(if (js/isFinite o)
@@ -924,12 +924,12 @@
924924
(m3-hash-int (hash-string o))
925925

926926
(instance? js/Date o)
927-
(.valueOf o)
927+
(bit-xor (.valueOf o) 0)
928928

929929
(nil? o) 0
930930

931931
:else
932-
(-hash o)))
932+
(bit-xor (-hash o) 0)))
933933

934934
(defn hash-combine [seed hash]
935935
; a la boost

src/test/cljs/cljs/hash_map_test.cljs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,38 @@
5050
(let [sym-a (with-meta 'foo :first)
5151
sym-b (with-meta 'foo :second)]
5252
(is (= {sym-a 2} (array-map sym-a 1 sym-b 2))))))
53+
54+
(defrecord T [index a b])
55+
56+
(deftest test-cljs-1976
57+
;; We must detect hash collisions when two values have different hashes but
58+
;; still have the same 32-bit hash. Hash producers may be lazy and not
59+
;; truncate their hash to 32-bits.
60+
(let [bad-record-1 (->T :eavt 17592186192276 nil)
61+
;; (hash bad-record-1) is 1454955434
62+
bad-record-2 (->T :avet 10 :fhir.ElementDefinition/minValueDateTime$cr)
63+
;; (hash bad-record-2) is -2840011862
64+
;; But (bit-or (hash bad-record-2) 0) is 1454955434. Collision!
65+
;; These dates have the same property
66+
bad-date-1 #inst "2017-03-13T22:21:08.666-00:00"
67+
bad-date-2 #inst "2015-11-02T19:53:15.706-00:00"]
68+
(testing "Transient assoc of hash-colliding keys with different hash values"
69+
(is (= :ok (try
70+
(hash-map bad-record-1 nil bad-record-2 nil)
71+
:ok
72+
(catch :default _ :error))))
73+
(is (= :ok (try
74+
(hash-map bad-date-1 nil bad-date-2 nil)
75+
:ok
76+
(catch :default _ :error)))))
77+
78+
(testing "Non-transient assoc of hash-colliding keys with different hash values"
79+
(is (= :ok (try
80+
(assoc (hash-map bad-record-1 nil) bad-record-2 nil)
81+
:ok
82+
(catch :default _ :error))))
83+
84+
(is (= :ok (try
85+
(assoc (hash-map bad-date-1 nil) bad-date-2 nil)
86+
:ok
87+
(catch :default _ :error)))))))

0 commit comments

Comments
 (0)