Skip to content

Commit d28e64d

Browse files
authored
fix: race condition when keys are added or removed while the object is being traversed (#1062)
1 parent fd20702 commit d28e64d

File tree

2 files changed

+13
-1
lines changed

2 files changed

+13
-1
lines changed

parse/src/main/java/com/parse/ParseObject.java

+3
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,9 @@ public Date getCreatedAt() {
14741474
/**
14751475
* Returns a set view of the keys contained in this object. This does not include createdAt,
14761476
* updatedAt, authData, or objectId. It does include things like username and ACL.
1477+
*
1478+
* <p>Note that while the returned set is unmodifiable, it is in fact not thread-safe, and
1479+
* creating a copy is recommended before iterating over it.
14771480
*/
14781481
public Set<String> keySet() {
14791482
synchronized (mutex) {

parse/src/main/java/com/parse/ParseTraverser.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
*/
99
package com.parse;
1010

11+
import java.util.HashSet;
1112
import java.util.IdentityHashMap;
1213
import java.util.Iterator;
1314
import java.util.List;
1415
import java.util.Map;
16+
import java.util.Set;
1517
import org.json.JSONArray;
1618
import org.json.JSONException;
1719
import org.json.JSONObject;
@@ -94,7 +96,14 @@ private void traverseInternal(
9496
} else if (root instanceof ParseObject) {
9597
if (traverseParseObjects) {
9698
ParseObject object = (ParseObject) root;
97-
for (String key : object.keySet()) {
99+
// Because the object's keySet is not thread safe, because the underlying Map isn't,
100+
// we need to create a copy before iterating over the object's keys to avoid
101+
// ConcurrentModificationExceptions
102+
Set<String> keySet;
103+
synchronized (object.mutex) {
104+
keySet = new HashSet<>(object.keySet());
105+
}
106+
for (String key : keySet) {
98107
traverseInternal(object.get(key), true, seen);
99108
}
100109
}

0 commit comments

Comments
 (0)