realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

[NSNull length]: unrecognized selector sent to instance

Open Cheezzhead opened this issue 1 year ago • 4 comments

How frequently does the bug occur?

Sometimes

Description

I have an object containing image data encoded as a base64 string. The image data propery may also be nil, indicating that the image is empty.

While there have generally been no issues so far, one user is encountering a crash when an array of the object is filtered based on whether the property is empty.

public final class Document: Object {
    // ...

    @Persisted public internal(set) var content: String?
    
    // ...

    public var isEmpty: { content?.isEmpty ?? true }
}

extension Collection where Element == Document {
    func filterNonEmpty() -> [Element] {
        self.filter { !$0.isEmpty } // Crashes with:  "-[NSNull length]: unrecognized selector sent to instance 0x1e1f00230"
    }
}

As far as I can find, the crash occurs when unwrapping the optional content property in the following method:

extension String: _PersistableInsideOptional, _DefaultConstructible, _PrimaryKey, _Indexable {
    public typealias PersistedType = String

    @inlinable
    public static func _rlmGetProperty(_ obj: ObjectBase, _ key: PropertyKey) -> String {
        return RLMGetSwiftPropertyString(obj, key)!
    }

    @inlinable
    public static func _rlmGetPropertyOptional(_ obj: ObjectBase, _ key: PropertyKey) -> String? {
        return RLMGetSwiftPropertyString(obj, key) // <-- Here
    }

    @inlinable
    public static func _rlmSetProperty(_ obj: ObjectBase, _ key: PropertyKey, _ value: String) {
        RLMSetSwiftPropertyString(obj, key, value)
    }
}

Other things of note:

  • We haven't heard of (or seen) other occurrances of this crash yet.
  • Using a backup of the database provided by the user, I can consistently reproduce the crash; however it occurs with a different (seemingly random) object in the array each time.
  • The array to be filtered contains ~350 objects, which is more than usual but (imo) not absurdly large.
  • CPU, memory and disk usage is all normal as far as I can see.
  • All objects in the array are frozen

Stacktrace & log output

#0	0x000000018bc54870 in __exceptionPreprocess ()
#1	0x0000000183fbfc00 in objc_exception_throw ()
#2	0x000000018bce519c in -[NSObject(NSObject) doesNotRecognizeSelector:] ()
#3	0x000000018bb99ff8 in ___forwarding___ ()
#4	0x000000018bcdab10 in _CF_forwarding_prep_0 ()
#5	0x000000018ab17eb8 in static String._unconditionallyBridgeFromObjectiveC(_:) ()
#6	0x0000000104ddd6d8 in static String._rlmGetPropertyOptional(_:_:) at /Users/***/Library/Developer/Xcode/DerivedData/App-drdtjhifxmbvpggmwkescoopfpvq/SourcePackages/checkouts/realm-swift/RealmSwift/Impl/BasicTypes.swift:222
#7	0x0000000104ddd7cc in protocol witness for static _PersistableInsideOptional._rlmGetPropertyOptional(_:_:) in conformance String ()
#8	0x0000000104de34dc in static Optional<τ_0_0>._rlmGetProperty(_:_:) at /Users/***/Library/Developer/Xcode/DerivedData/App-drdtjhifxmbvpggmwkescoopfpvq/SourcePackages/checkouts/realm-swift/RealmSwift/Impl/ComplexTypes.swift:244
#9	0x0000000104de37d0 in protocol witness for static _Persistable._rlmGetProperty(_:_:) in conformance <τ_0_0> τ_0_0? ()
#10	0x0000000104e3ea20 in Persisted.get(_:) at /Users/***/Library/Developer/Xcode/DerivedData/App-drdtjhifxmbvpggmwkescoopfpvq/SourcePackages/checkouts/realm-swift/RealmSwift/PersistedProperty.swift:181
#11	0x0000000104e3dc78 in static Persisted.subscript.getter at /Users/***/Library/Developer/Xcode/DerivedData/App-drdtjhifxmbvpggmwkescoopfpvq/SourcePackages/checkouts/realm-swift/RealmSwift/PersistedProperty.swift:126
#12	0x0000000105e79e08 in Document.content.getter ()
#13	0x0000000105e85c14 in protocol witness for _DocImage.content.getter in conformance Document ()
#14	0x0000000105c190d4 in _DocImage.isEmpty.getter at /Users/***/Developer/Core/Sources/Core/Image/DocImage.swift:57

Can you reproduce the bug?

Always

Reproduction Steps

No response

Version

10.45.3

What Atlas Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

iOS 17

Build environment

Xcode version: 15.2 (15C500b) Dependency manager and version: SPM

Cheezzhead avatar Jan 16 '24 12:01 Cheezzhead

I am having some difficulty replicating the issue. This section of code

public final class Document: Object {
    @Persisted public internal(set) var content: String?
    public var isEmpty: { content?.isEmpty ?? true }
}

Will not compile: the public var isEmpty: { content?.isEmpty ?? true } has an expected type error in XCode.

Also, as a side note and question:

Realm is not well suited for storing Blob type data - it's generally best practice to store images using a different service. That being said, smaller blobs are fine - which leaves the question, how large are the encoded strings? Images can grow quite large and can exceed the 16Mb limit pretty easily.

What is the function of extension String with the @inlinable directives. Those are often reserved for libraries or maybe a framework.

Jaycyn avatar Jan 16 '24 18:01 Jaycyn

Ah yes, the code I posted is a heavily simplified version of the production code, indeed the code public var isEmpty: { content?.isEmpty ?? true } is missing the type identifier.

The images are resized prior to storing, so they rarely (if ever) exceed 16MB. However I didn't know that large blobs of data are not a good fit for Realm. Long-term we will probably migrate to storing the images as a file and only persist the URL.

Having said that, that doesn't explain the crash; it occurs semi-randomly and seems to be related to memory availability. The crash comes after several filter functions, most of them using the isEmpty property; meaning that property can be called potentially several times in one call. This is not really optimal, but in the case of Realm, it also turns out that memory is not freed up where one would reasonably expect it to. Which is not a problem until large amounts of data are handled at once. I managed to fix this issue (or rather, the crash) by using an autoreleasepool closure at specific points of the operation.

What is the function of extension String with the @inlinable directives. Those are often reserved for libraries or maybe a framework.

That code is from the RealmSwift framework.

Cheezzhead avatar Jan 24 '24 10:01 Cheezzhead

Based on

Ah yes, the code I posted is a heavily simplified version of the production code, indeed the code public var isEmpty: { content?.isEmpty ?? true } is missing the type identifier.

Can you update the question or add some details about how that function actually works? It may be related to the crash since the isEmpty var is involved (or maybe not, but want have more complete code just in case)

Jaycyn avatar Feb 12 '24 21:02 Jaycyn

I spoke with the team and this does look like a bug on our end, but it's not at all obvious what could be causing it. If you are able to produce a minimal repro case, that would go a long way toward identifying the root cause and fixing it.

nirinchev avatar Feb 12 '24 21:02 nirinchev

I've tried creating a minimal repro case by loading 1000+ objects with image data - either as Data or as base64 encoded String - and then superfluously filtering on the isEmpty property several times, but all it gave me was a huge memory usage and no crash. The original production code that caused this issue had several more layers of filtering and passing of data in the background but like I said, I can't seem to reproduce it now.

The problem in our production app is solved by storing the images on disk and persisting the URL only, so unless someone has anything to add I think it's better to close this issue for now.

Cheezzhead avatar Feb 21 '24 14:02 Cheezzhead