Skip to content

Commit e839f3f

Browse files
author
Nicolas Laurent
committed
add a wrapper around keys in WeakMap to ensure correct ruby reference comparison
& hashing semantics
1 parent df35b7c commit e839f3f

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.core.hash;
11+
12+
import org.truffleruby.core.basicobject.BasicObjectNodes.ReferenceEqualNode;
13+
import org.truffleruby.core.hash.HashingNodes.ToHashByIdentity;
14+
15+
/** Wraps a value so that it will compared and hashed according to Ruby identity semantics. These semantics differ from
16+
* Java semantics notably for primitives (e.g. all the NaN are different according to Ruby, but Java compares them
17+
* equal). */
18+
public final class CompareByRubyIdentityWrapper {
19+
20+
public final Object value;
21+
22+
public CompareByRubyIdentityWrapper(Object value) {
23+
this.value = value;
24+
}
25+
26+
@Override
27+
public int hashCode() {
28+
return ToHashByIdentity.getUncached().execute(value);
29+
}
30+
31+
@Override
32+
public boolean equals(Object obj) {
33+
return obj instanceof CompareByRubyIdentityWrapper &&
34+
ReferenceEqualNode
35+
.getUncached()
36+
.executeReferenceEqual(value, ((CompareByRubyIdentityWrapper) obj).value);
37+
}
38+
}

src/main/java/org/truffleruby/core/objectspace/WeakMapNodes.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
import org.truffleruby.builtins.Primitive;
1616
import org.truffleruby.builtins.UnaryCoreMethodNode;
1717
import org.truffleruby.builtins.YieldingCoreMethodNode;
18-
import org.truffleruby.collections.WeakValueCache;
18+
import org.truffleruby.collections.WeakValueCache.WeakMapEntry;
1919
import org.truffleruby.core.array.RubyArray;
20+
import org.truffleruby.core.hash.CompareByRubyIdentityWrapper;
2021
import org.truffleruby.core.klass.RubyClass;
2122
import org.truffleruby.core.proc.RubyProc;
2223
import org.truffleruby.language.Nil;
@@ -27,6 +28,8 @@
2728
import com.oracle.truffle.api.dsl.Specialization;
2829
import org.truffleruby.language.objects.AllocationTracing;
2930

31+
import java.util.Collection;
32+
3033
/** Note that WeakMap uses identity comparison semantics. See top comment in src/main/ruby/truffleruby/core/weakmap.rb
3134
* for more information. */
3235
@CoreModule(value = "ObjectSpace::WeakMap", isClass = true)
@@ -57,7 +60,7 @@ public abstract static class MemberNode extends CoreMethodArrayArgumentsNode {
5760

5861
@Specialization
5962
protected boolean isMember(RubyWeakMap map, Object key) {
60-
return map.storage.get(key) != null;
63+
return map.storage.get(new CompareByRubyIdentityWrapper(key)) != null;
6164
}
6265
}
6366

@@ -66,7 +69,7 @@ public abstract static class GetIndexNode extends CoreMethodArrayArgumentsNode {
6669

6770
@Specialization
6871
protected Object get(RubyWeakMap map, Object key) {
69-
Object value = map.storage.get(key);
72+
Object value = map.storage.get(new CompareByRubyIdentityWrapper(key));
7073
return value == null ? nil : value;
7174
}
7275
}
@@ -76,7 +79,7 @@ public abstract static class SetIndexNode extends CoreMethodArrayArgumentsNode {
7679

7780
@Specialization
7881
protected Object set(RubyWeakMap map, Object key, Object value) {
79-
map.storage.put(key, value);
82+
map.storage.put(new CompareByRubyIdentityWrapper(key), value);
8083
return value;
8184
}
8285
}
@@ -144,8 +147,8 @@ protected RubyWeakMap each(RubyWeakMap map, Nil block) {
144147
@Specialization
145148
protected RubyWeakMap each(RubyWeakMap map, RubyProc block) {
146149

147-
for (WeakValueCache.WeakMapEntry<?, ?> e : entries(map.storage)) {
148-
yield(block, e.getKey(), e.getValue());
150+
for (MapEntry entry : entries(map.storage)) {
151+
yield(block, entry.key, entry.value);
149152
}
150153

151154
return map;
@@ -154,17 +157,39 @@ protected RubyWeakMap each(RubyWeakMap map, RubyProc block) {
154157

155158
@TruffleBoundary
156159
private static Object[] keys(WeakMapStorage storage) {
157-
return storage.keys().toArray();
160+
final Collection<CompareByRubyIdentityWrapper> keyWrappers = storage.keys();
161+
final Object[] keys = new Object[keyWrappers.size()];
162+
int i = 0;
163+
for (CompareByRubyIdentityWrapper keyWrapper : keyWrappers) {
164+
keys[i++] = keyWrapper.value;
165+
}
166+
return keys;
158167
}
159168

160169
@TruffleBoundary
161170
private static Object[] values(WeakMapStorage storage) {
162171
return storage.values().toArray();
163172
}
164173

174+
private static class MapEntry {
175+
final Object key;
176+
final Object value;
177+
178+
private MapEntry(Object key, Object value) {
179+
this.key = key;
180+
this.value = value;
181+
}
182+
}
183+
165184
@TruffleBoundary
166-
private static WeakValueCache.WeakMapEntry<?, ?>[] entries(WeakMapStorage storage) {
167-
return storage.entries().toArray(new WeakValueCache.WeakMapEntry<?, ?>[0]);
185+
private static MapEntry[] entries(WeakMapStorage storage) {
186+
final Collection<WeakMapEntry<CompareByRubyIdentityWrapper, Object>> wrappedEntries = storage.entries();
187+
final MapEntry[] entries = new MapEntry[wrappedEntries.size()];
188+
int i = 0;
189+
for (WeakMapEntry<CompareByRubyIdentityWrapper, Object> wrappedEntry : wrappedEntries) {
190+
entries[i++] = new MapEntry(wrappedEntry.getKey().value, wrappedEntry.getValue());
191+
}
192+
return entries;
168193
}
169194

170195
private static RubyWeakMap eachNoBlockProvided(YieldingCoreMethodNode node, RubyWeakMap map) {

src/main/java/org/truffleruby/core/objectspace/WeakMapStorage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.truffleruby.core.objectspace;
1111

1212
import org.truffleruby.collections.WeakValueCache;
13+
import org.truffleruby.core.hash.CompareByRubyIdentityWrapper;
1314

14-
public final class WeakMapStorage extends WeakValueCache<Object, Object> {
15+
public final class WeakMapStorage extends WeakValueCache<CompareByRubyIdentityWrapper, Object> {
1516
}

0 commit comments

Comments
 (0)