From 5d480c8e557b298285a618e7393062cb47342eab Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Wed, 13 Feb 2019 19:33:48 -0500 Subject: [PATCH 1/3] Add test for StackOverflowError during mergeFromServer This test shows the #896 bug. --- .../test/java/com/parse/ParseObjectTest.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/parse/src/test/java/com/parse/ParseObjectTest.java b/parse/src/test/java/com/parse/ParseObjectTest.java index 8a4c0504b..bd1212953 100644 --- a/parse/src/test/java/com/parse/ParseObjectTest.java +++ b/parse/src/test/java/com/parse/ParseObjectTest.java @@ -44,6 +44,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -128,7 +129,7 @@ public void testFromJSONPayload() throws JSONException { //endregion - //region testGetter + //region testFromJson @Test public void testFromJSONPayloadWithoutClassname() throws JSONException { @@ -137,6 +138,30 @@ public void testFromJSONPayloadWithoutClassname() throws JSONException { assertNull(parseObject); } + @Test + public void testFromJsonWithLdsStackOverflow() throws JSONException { + ParseObject localObj = ParseObject.createWithoutData("GameScore", "TT1ZskATqS"); + OfflineStore lds = mock(OfflineStore.class); + Parse.setLocalDatastore(lds); + + when(lds.getObject(eq("GameScore"), eq("TT1ZskATqS"))).thenReturn(localObj); + + JSONObject json = new JSONObject("{" + + "\"className\":\"GameScore\"," + + "\"createdAt\":\"2015-06-22T21:23:41.733Z\"," + + "\"objectId\":\"TT1ZskATqS\"," + + "\"updatedAt\":\"2015-06-22T22:06:18.104Z\"" + + "}"); + ParseObject obj; + for (int i = 0; i < 50000; i++) { + obj = ParseObject.fromJSON(json, "GameScore", ParseDecoder.get(), Collections.emptySet()); + } + } + + //endregion + + //region testGetter + @Test public void testRevert() throws ParseException { List> tasks = new ArrayList<>(); From 5b04c7ca15897ada0de3896eeccfccd275b2052f Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Wed, 13 Feb 2019 19:35:48 -0500 Subject: [PATCH 2/3] Make a synchronized copy of availableKeys `synchronizedSet(s)` ends up making a new wrapper for the supplied set, which means on subsequent calls, it becomes a SynchronizedSet, wrapped in a SynchronizedSet, wrapped ... etc. When using the LocalDataStore, the ParseObject.State is reused for every matching ClassName+ObjectId pair, so every time a ParseObject is parsed from JSON data, the existing object is "refreshed" by making a copy of its State, and then merging with the new data. Every call to State.newBuilder() is therefore adding a new layer of SynchronizedSet to the availableKeys Set. Eventually that nested hierarchy of sets becomes so large that it causes a StackOverflowError for any operation on the collection. By making a copy of the set before wrapping it in synchronizedSet(), that nested hierarchy becomes severed, and we end up with just one level of wrappers. --- parse/src/main/java/com/parse/ParseObject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse/src/main/java/com/parse/ParseObject.java b/parse/src/main/java/com/parse/ParseObject.java index a9cab958b..d8c2fc5fc 100644 --- a/parse/src/main/java/com/parse/ParseObject.java +++ b/parse/src/main/java/com/parse/ParseObject.java @@ -4076,7 +4076,7 @@ public Init(String className) { objectId = state.objectId(); createdAt = state.createdAt(); updatedAt = state.updatedAt(); - availableKeys = Collections.synchronizedSet(state.availableKeys()); + availableKeys = Collections.synchronizedSet(new HashSet<>(state.availableKeys())); for (String key : state.keySet()) { serverData.put(key, state.get(key)); availableKeys.add(key); From 0c3dfd2929b15a453d93ea936feff746aa718820 Mon Sep 17 00:00:00 2001 From: Joe Hansche Date: Wed, 13 Feb 2019 20:03:57 -0500 Subject: [PATCH 3/3] Accept the updated Android Build-Tools 27 license --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6888f7b24..663381eef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ jdk: before_install: - pip install --user codecov - mkdir "$ANDROID_HOME/licenses" || true - - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" + - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" >> "$ANDROID_HOME/licenses/android-sdk-license" script: - ./gradlew clean testDebugUnitTest jacocoTestReport