Skip to content

Commit 06376ba

Browse files
cpovirkGoogle Java Core Libraries
authored andcommitted
Clear the fields of a Node inside delete.
This ensures that we don't retain memory to other `Node` instances and their contents, even in unlikely scenarios in which the user is holding a reference to one specific `Node` (through an `Entry`) but not to the others that it refers to. This requires reading those fields in advance when replacing a node. But given that we already had complex ordering rules to follow in `putInverse`, I think that this is an improvement. (This change was prompted by investigation in 2097440.) RELNOTES=n/a PiperOrigin-RevId: 846336997
1 parent bfbc624 commit 06376ba

File tree

1 file changed

+58
-63
lines changed

1 file changed

+58
-63
lines changed

guava/src/com/google/common/collect/HashBiMap.java

Lines changed: 58 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,7 @@ private void init(int expectedSize) {
156156

157157
/**
158158
* Finds and removes {@code node} from the key-to-value hash table, the value-to-key hash table,
159-
* and the iteration-order chain, leaving its own references to other nodes intact.
160-
*/
161-
/*
162-
* TODO: cl/778043974 - Currently, callers are inconsistent about whether they clear the node's
163-
* references to other nodes: None clear the bucket-chain fields, but some clear the
164-
* iteration-order fields. (Some callers need to be careful not to clear the iteration-order
165-
* fields too soon.) Do we need to clear fields at all, given that this Node will no longer be
166-
* referenced after it's deleted from the map (except possibly through Entry objects from an
167-
* earlier iteration over the map)? If we do, should we be more consistent about it?
159+
* and the iteration-order chain. This includes clearing its own references to other entries.
168160
*/
169161
private void delete(Node<K, V> node) {
170162
int keyBucket = node.keyHash & mask;
@@ -211,11 +203,49 @@ private void delete(Node<K, V> node) {
211203
node.nextInKeyInsertionOrder.prevInKeyInsertionOrder = node.prevInKeyInsertionOrder;
212204
}
213205

206+
node.prevInKeyInsertionOrder = null;
207+
node.nextInKeyInsertionOrder = null;
208+
node.nextInKToVBucket = null;
209+
node.nextInVToKBucket = null;
210+
214211
size--;
215212
modCount++;
216213
}
217214

218-
private void insert(Node<K, V> node, @Nullable Node<K, V> oldNodeForKey) {
215+
private void insertPlacingAtEndOfIterationOrder(Node<K, V> node) {
216+
insertIntoHashBucketsOnly(node);
217+
218+
node.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
219+
if (lastInKeyInsertionOrder == null) {
220+
firstInKeyInsertionOrder = node;
221+
} else {
222+
lastInKeyInsertionOrder.nextInKeyInsertionOrder = node;
223+
}
224+
lastInKeyInsertionOrder = node;
225+
}
226+
227+
private void insertSplicingIntoIterationOrder(
228+
Node<K, V> node,
229+
@Nullable Node<K, V> prevInKeyInsertionOrder,
230+
@Nullable Node<K, V> nextInKeyInsertionOrder) {
231+
insertIntoHashBucketsOnly(node);
232+
233+
node.prevInKeyInsertionOrder = prevInKeyInsertionOrder;
234+
if (prevInKeyInsertionOrder == null) {
235+
firstInKeyInsertionOrder = node;
236+
} else {
237+
prevInKeyInsertionOrder.nextInKeyInsertionOrder = node;
238+
}
239+
240+
node.nextInKeyInsertionOrder = nextInKeyInsertionOrder;
241+
if (nextInKeyInsertionOrder == null) {
242+
lastInKeyInsertionOrder = node;
243+
} else {
244+
nextInKeyInsertionOrder.prevInKeyInsertionOrder = node;
245+
}
246+
}
247+
248+
private void insertIntoHashBucketsOnly(Node<K, V> node) {
219249
int keyBucket = node.keyHash & mask;
220250
node.nextInKToVBucket = hashTableKToV[keyBucket];
221251
hashTableKToV[keyBucket] = node;
@@ -224,34 +254,17 @@ private void insert(Node<K, V> node, @Nullable Node<K, V> oldNodeForKey) {
224254
node.nextInVToKBucket = hashTableVToK[valueBucket];
225255
hashTableVToK[valueBucket] = node;
226256

227-
if (oldNodeForKey == null) {
228-
node.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
229-
node.nextInKeyInsertionOrder = null;
230-
if (lastInKeyInsertionOrder == null) {
231-
firstInKeyInsertionOrder = node;
232-
} else {
233-
lastInKeyInsertionOrder.nextInKeyInsertionOrder = node;
234-
}
235-
lastInKeyInsertionOrder = node;
236-
} else {
237-
node.prevInKeyInsertionOrder = oldNodeForKey.prevInKeyInsertionOrder;
238-
if (node.prevInKeyInsertionOrder == null) {
239-
firstInKeyInsertionOrder = node;
240-
} else {
241-
node.prevInKeyInsertionOrder.nextInKeyInsertionOrder = node;
242-
}
243-
node.nextInKeyInsertionOrder = oldNodeForKey.nextInKeyInsertionOrder;
244-
if (node.nextInKeyInsertionOrder == null) {
245-
lastInKeyInsertionOrder = node;
246-
} else {
247-
node.nextInKeyInsertionOrder.prevInKeyInsertionOrder = node;
248-
}
249-
}
250-
251257
size++;
252258
modCount++;
253259
}
254260

261+
private void replaceNodeForKey(Node<K, V> oldNode, Node<K, V> newNode) {
262+
Node<K, V> prevInKeyInsertionOrder = oldNode.prevInKeyInsertionOrder;
263+
Node<K, V> nextInKeyInsertionOrder = oldNode.nextInKeyInsertionOrder;
264+
delete(oldNode); // clears the two fields we just read
265+
insertSplicingIntoIterationOrder(newNode, prevInKeyInsertionOrder, nextInKeyInsertionOrder);
266+
}
267+
255268
private @Nullable Node<K, V> seekByKey(@Nullable Object key, int keyHash) {
256269
for (Node<K, V> node = hashTableKToV[keyHash & mask];
257270
node != null;
@@ -327,13 +340,10 @@ public boolean containsValue(@Nullable Object value) {
327340

328341
Node<K, V> newNode = new Node<>(key, keyHash, value, valueHash);
329342
if (oldNodeForKey != null) {
330-
delete(oldNodeForKey);
331-
insert(newNode, oldNodeForKey);
332-
oldNodeForKey.prevInKeyInsertionOrder = null;
333-
oldNodeForKey.nextInKeyInsertionOrder = null;
343+
replaceNodeForKey(oldNodeForKey, newNode);
334344
return oldNodeForKey.value;
335345
} else {
336-
insert(newNode, null);
346+
insertPlacingAtEndOfIterationOrder(newNode);
337347
rehashIfNecessary();
338348
return null;
339349
}
@@ -361,31 +371,18 @@ public boolean containsValue(@Nullable Object value) {
361371
throw new IllegalArgumentException("key already present: " + key);
362372
}
363373

364-
/*
365-
* The ordering here is important: if we deleted the key node and then the value node, the key
366-
* node's prev or next pointer might point to the dead value node, and when we put the new node
367-
* in the key node's position in iteration order, it might invalidate the linked list.
368-
*/
369-
370374
if (oldNodeForValue != null) {
371375
delete(oldNodeForValue);
372376
}
373377

374-
if (oldNodeForKey != null) {
375-
delete(oldNodeForKey);
376-
}
377-
378378
Node<K, V> newNode = new Node<>(key, keyHash, value, valueHash);
379-
insert(newNode, oldNodeForKey);
380-
381379
if (oldNodeForKey != null) {
382-
oldNodeForKey.prevInKeyInsertionOrder = null;
383-
oldNodeForKey.nextInKeyInsertionOrder = null;
384-
}
385-
if (oldNodeForValue != null) {
386-
oldNodeForValue.prevInKeyInsertionOrder = null;
387-
oldNodeForValue.nextInKeyInsertionOrder = null;
380+
replaceNodeForKey(oldNodeForKey, newNode);
381+
} else {
382+
insertPlacingAtEndOfIterationOrder(newNode);
388383
}
384+
385+
// TODO(cpovirk): Don't perform rehash check if we replaced an existing entry (as in `put`)?
389386
rehashIfNecessary();
390387
return keyOrNull(oldNodeForValue);
391388
}
@@ -403,7 +400,8 @@ private void rehashIfNecessary() {
403400
for (Node<K, V> node = firstInKeyInsertionOrder;
404401
node != null;
405402
node = node.nextInKeyInsertionOrder) {
406-
insert(node, node);
403+
insertSplicingIntoIterationOrder(
404+
node, node.prevInKeyInsertionOrder, node.nextInKeyInsertionOrder);
407405
}
408406
this.modCount++;
409407
}
@@ -574,11 +572,8 @@ public V setValue(@ParametricNullness V value) {
574572
return value;
575573
}
576574
checkArgument(seekByValue(value, valueHash) == null, "value already present: %s", value);
577-
delete(node);
578575
Node<K, V> newNode = new Node<>(node.key, node.keyHash, value, valueHash);
579-
insert(newNode, node);
580-
node.prevInKeyInsertionOrder = null;
581-
node.nextInKeyInsertionOrder = null;
576+
replaceNodeForKey(node, newNode);
582577
expectedModCount = modCount;
583578
if (Objects.equals(toRemove, node)) {
584579
toRemove = newNode;
@@ -767,7 +762,7 @@ private K setObverseKey(@ParametricNullness K obverseKey) {
767762
obverseKey);
768763
obverse.delete(node);
769764
Node<K, V> newNode = new Node<>(obverseKey, obverseKeyHash, node.value, node.valueHash);
770-
obverse.insert(newNode, /* oldNodeForKey= */ null);
765+
obverse.insertPlacingAtEndOfIterationOrder(newNode);
771766
expectedModCount = obverse.modCount;
772767
K oldObverseKey = node.key;
773768
if (Objects.equals(toRemove, node)) {

0 commit comments

Comments
 (0)