Skip to content

Fix StackOverflowError when merging ParseObject from JSON #925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

jhansche
Copy link
Contributor

This fixes #896. See comments there, and at #795.

When LocalDataStore is enabled, the same ParseObject(.State) is shared for the same class+id pair. That allows us to update objects in-place whenever a ParseObject is parsed from JSON. But every time the state is copied, via State.newBuilder(), the availableKeys Set is wrapped (and re-wrapped, etc...) in synchronizedSet().

Eventually, if the object has been refreshed enough times, that results in such a deeply nested hierarchy of wrapped sets, that any collection operation on it results in a StackOverflowError.

This change adds a test to prove the bug, and fixes it by severing making a copy of the set before wrapping it in synchronizedSet().

`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.
@jhansche jhansche added the type:bug Impaired feature or lacking behavior that is likely assumed label Feb 14, 2019
@jhansche jhansche self-assigned this Feb 14, 2019
@codecov
Copy link

codecov bot commented Feb 14, 2019

Codecov Report

Merging #925 into master will decrease coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #925      +/-   ##
============================================
- Coverage     64.65%   64.64%   -0.02%     
  Complexity     1898     1898              
============================================
  Files           122      122              
  Lines          9709     9709              
  Branches       1361     1361              
============================================
- Hits           6277     6276       -1     
  Misses         2927     2927              
- Partials        505      506       +1
Impacted Files Coverage Δ Complexity Δ
parse/src/main/java/com/parse/ParseObject.java 59.71% <100%> (ø) 235 <0> (+1) ⬆️
...arse/src/main/java/com/parse/ParseCorePlugins.java 66.66% <0%> (-0.56%) 50% <0%> (-1%)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5bd91aa...0c3dfd2. Read the comment docs.

@coveralls
Copy link

coveralls commented Feb 14, 2019

Coverage Status

Coverage remained the same at 69.853% when pulling 0c3dfd2 on wondrous-io:pr/fix-896-stackoverflow into 5bd91aa on parse-community:master.

Copy link
Member

@Jawnnypoo Jawnnypoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, other than some minor changes

@jhansche jhansche force-pushed the pr/fix-896-stackoverflow branch from 26eec70 to 0c3dfd2 Compare February 14, 2019 15:20
@Jawnnypoo Jawnnypoo merged commit f2e6475 into parse-community:master Feb 14, 2019
@AlejandroHCruz
Copy link

Hi, now that this is merged, when could we expect a new version of the library to be released?

@Jawnnypoo
Copy link
Member

I'll wait for EOD for a review of #926 and then I'll merge and create 1.19.0

@AlejandroHCruz
Copy link

Awesome thanks… well, I guess this is as far as I go and now I need to migrate the project to AndroidX 😅 wish me luck

@Jawnnypoo
Copy link
Member

Refactor -> Migrate to Android X works great in Android Studio!

@AlejandroHCruz
Copy link

Yeah, just tried it out again and have some problems with the native C++ code 😅

@jhansche
Copy link
Contributor Author

It might be a good idea to release this change as 1.18.6, and then 1.19 separately with the AndroidX update. Since that change breaks compatibility, but this one didn't.

We can still do a retro tag and release of f2e6475. What do you think?

cc @rogerhu @Jawnnypoo

@AlejandroHCruz
Copy link

It would be really amazing if you could do that 👍

@Jawnnypoo
Copy link
Member

Sure, it has been released as 1.18.6

@jhansche jhansche deleted the pr/fix-896-stackoverflow branch February 19, 2019 16:48
@jhansche
Copy link
Contributor Author

Thanks!

@nisrulz
Copy link

nisrulz commented Feb 19, 2019

Hi,
Thank you for your support and for making the release 1.18.6 available. I was looking through release and noticed that the dependencies on this release weren't updated although they were done so on the 1.19.0 release.

buildscript {
    ext.kotlin_version = '1.2.71'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.3'
        classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

plugins {
    id 'com.github.ben-manes.versions' version '0.20.0'
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

ext {
    compileSdkVersion = 27

    supportLibVersion = '27.1.1'

    firebaseJobdispatcherVersion = '0.8.5'

    minSdkVersion = 14
    targetSdkVersion = 27
}

Any reason to not do so?

If it is fine, I can open a PR to update those :)

@Jawnnypoo
Copy link
Member

1.18.6 is sort of a special case for those who cannot move to AndroidX yet, but want the fixes of this PR. 1.19.0 is the preferred upgrade path for anyone who is targeting API 28 and all future versions will build on this.

@jhansche
Copy link
Contributor Author

jhansche commented Mar 13, 2019

@Jawnnypoo did any of those releases make it to Maven Central? Or did I miss something wrt where artifacts are deployed these days?

https://mvnrepository.com/artifact/com.parse/parse-android shows the latest version is 1.17.3

EDIT: disregard :) I see the notes about jitpack

@rogerhu
Copy link
Contributor

rogerhu commented Mar 13, 2019

Yeah I kind of wished we had it back to maven central. It's created a lot of confusion..

@Jawnnypoo
Copy link
Member

Feel free to move it back if you would like, I think jitpack makes the releases much easier (less configuration) and just requires creating a tag, which is simple and allowed me to create 1.18.6 very easily by just creating a tag of a commit that hadn't even been released previously. Easier access to SNAPSHOTS and versioning by a particular commit (pre release) as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ParseObject.availableKeys - java.lang.StackOverflowError (Only Android 4x)
6 participants