Skip to content
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

Firestore Crash with Empty Blob field #11773

Closed
harry-dickson opened this issue Sep 5, 2023 · 9 comments
Closed

Firestore Crash with Empty Blob field #11773

harry-dickson opened this issue Sep 5, 2023 · 9 comments
Assignees

Comments

@harry-dickson
Copy link

harry-dickson commented Sep 5, 2023

Description

I have a Document with a Blob field that is null empty.
In my Flutter app, when I fetch the document there is a crash.

The crash occurs in Firestore/core/src/nanopb/nanopb_util.h:

// line 181
inline NSData* _Nonnull MakeNSData(const pb_bytes_array_t* _Nullable data) {
  return [[NSData alloc] initWithBytes:data->bytes length:data->size];
}

The data is declared as being nullable, null is passed in and then dereferenced without any attempt to check it.
The return type is not nullable, making reasonable behavior difficult. Either it should return something in the face of null, or it should never be called with null in the first place. Allowing the app to crash is not a cool outcome.

This is the calling code in FSTUserDataWriter.mm:

// line 81
- (id)convertedValue:(const google_firestore_v1_Value &)value {
  switch (GetTypeOrder(value)) {
    case TypeOrder::kMap:
      return [self convertedObject:value.map_value];
    case TypeOrder::kArray:
      return [self convertedArray:value.array_value];
    case TypeOrder::kReference:
      return [self convertedReference:value];
    case TypeOrder::kTimestamp:
      return [self convertedTimestamp:value.timestamp_value];
    case TypeOrder::kServerTimestamp:
      return [self convertedServerTimestamp:value];
    case TypeOrder::kNull:
      return [NSNull null];
    case TypeOrder::kBoolean:
      return value.boolean_value ? @YES : @NO;
    case TypeOrder::kNumber:
      return value.which_value_type == google_firestore_v1_Value_integer_value_tag
                 ? @(value.integer_value)
                 : @(value.double_value);
    case TypeOrder::kString:
      return MakeNSString(MakeStringView(value.string_value));
    case TypeOrder::kBlob:
      return MakeNSData(value.bytes_value);  // <<============= Here: value.bytes_value is nil
    case TypeOrder::kGeoPoint:
      return MakeFIRGeoPoint(
          GeoPoint(value.geo_point_value.latitude, value.geo_point_value.longitude));
    case TypeOrder::kMaxValue:
      // It is not possible for users to construct a kMaxValue manually.
      break;
  }

  UNREACHABLE();
}

Reproducing the issue

Store a document with a null Blob field. Now read it.

Firebase SDK Version

10.12

Xcode Version

14.2

Installation Method

CocoaPods

Firebase Product(s)

Firestore

Targeted Platforms

iOS

Relevant Log Output

No response

If using Swift Package Manager, the project's Package.resolved

N/A

If using CocoaPods, the project's Podfile.lock

N/A

@google-oss-bot
Copy link

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@ehsannas
Copy link
Contributor

ehsannas commented Sep 5, 2023

thanks for reporting @harry-dickson . I'll take a look

@ehsannas ehsannas self-assigned this Sep 5, 2023
@ehsannas
Copy link
Contributor

ehsannas commented Sep 5, 2023

@harry-dickson , can you please share the code snippet you're using to store and read back the null blob?

I expected a field value of null to fall under

    case TypeOrder::kNull:
      return [NSNull null];
@harry-dickson
Copy link
Author

I'll take a closer look tomorrow, but it's Flutter/Dart and I believe it's set though an update:

  DocumentRef<Stuff> ref;
  ref.update({"blob": null});

I think I can prevent this in the case of null as a workaround - and fix the handful of broken documents
(the field is an optional, encoded profile picture)

The read is coming through a subscription:

  DocumentRef<Stuff> ref;
  ref.snapshots().listen(...)

The initial load that's triggered by the listen causes the crash before it gets near the flutter code.

@harry-dickson
Copy link
Author

harry-dickson commented Sep 6, 2023

I'll take a closer look tomorrow, but it's Flutter/Dart and I believe it's set though an update:
...

The initial load that's triggered by the listen causes the crash before it gets near the flutter code.

That's not right. If it is null in the source object, it is simply omitted in the Map provided to the update.
It can be an empty byte array however. Is it possible the Blob handling would transmute that into a null?

It seems plausible from a simple text search in xcode:

nanopb_utils.cc:

pb_bytes_array_t* _Nullable MakeBytesArray(const void* _Nullable data,
                                           size_t size) {
  if (size == 0) return nullptr;

  // Handle non-empty data...
}

Called from MakeByteString() nanopb_util.h line 170
Called from parseScalarValue() FSTUserDataReader.mm line 519

  } else if ([input isKindOfClass:[NSData class]]) {
    NSData *inputData = input;
    return [self encodeBlob:(nanopb::MakeByteString(inputData))];
  }

I will try and stop writing empty arrays in my code as a workaround.

@harry-dickson
Copy link
Author

harry-dickson commented Sep 6, 2023

I get this crash for data that looks like this in the firebase console:

  foo: "value"         (string)
  bar: 99              (number)
  blob:                (blob)

That is, the field is present, but void/empty.
Any nulls in the database was probably just me frobbing around in the console investigating this issue.

In Flutter (package cloud_firestorm_platform_interface:blob.dart) it is not legal to store a null Blob.
The bytes member in the Blob constructor is not nullable. So I am definitely never sending null to be stored.

Looks like empty Blobs are coming from the database into the ios sdk typed as a Blob but with null as value.

@harry-dickson
Copy link
Author

Something did change back in May/June time: firebase/flutterfire#11096
I ended up staring at the same code then: FSTUserDataReader/Writer.

@harry-dickson harry-dickson changed the title Firestore Crash with Null Blob field Sep 6, 2023
@harry-dickson
Copy link
Author

My workaround is to store an empty String in place of an empty Blob which works for my data assumptions; ymmv:

  static Map<String, dynamic> writeBytes(
    Map<String, dynamic> buf,
    String attr,
    Uint8List? bytes,
  ) {
    if (bytes != null) {
      if (bytes.isEmpty) {
        buf[attr] = "";
      } else {
        buf[attr] = Blob(bytes);
      }
    }
    return buf;
  }
@tom-andersen
Copy link
Contributor

Fix will be part of next release.

@firebase firebase locked and limited conversation to collaborators May 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
5 participants