Skip to content

[cloud_firestore]: Apple pipeline parser passes an NSString where FIRExprBridge is expected for findNearest distance_field (incompatible pointer types warning; unrecognized selector crash if reached) #18829

Closed

Description

@DebuggerTr-Consulting

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Other (cloud_firestore)

Which platforms are affected?

iOS, macOS

Description

In the Apple pipeline parser, the find_nearest stage reads distance_field as an NSString and passes it straight to the SDK's distanceField: parameter. That parameter is typed as an expression bridge (FIRExprBridge * / __FIRExprBridge * on SDK 13).

Build time: every iOS/macOS build prints this warning:

cloud_firestore-6.10.0/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m:1570:66: warning: incompatible pointer types sending 'NSString *' to parameter of type 'FIRExprBridge * _Nullable' [-Wincompatible-pointer-types]
 1570 |                                                    distanceField:distanceField];
      |                                                                  ^~~~~~~~~~~~~

Run time: the SDK stores the value and later sends it cppExprWithReader:, a method that NSString does not have. So any find_nearest stage that carries distance_field throws an uncaught NSInvalidArgumentException (unrecognized selector), and the app terminates.

Where

The plugin code is the same in every release since pipelines shipped, including the latest one:

Version iOS file:line
6.3.0 – 6.4.1 FLTPipelineParser.m:1105 (read), :1137 (pass)
6.5.0 :1188 (read), :1220 (pass)
6.9.0 – 6.10.0 :1538 (read), :1570 (pass)
7.0.0 (latest on pub.dev) FLTPipelineParser.m:1578 (read), :1610 (pass)
main @ 9b821bc18500b9ae55a9cb74b0be949cb64bcd19 same as 7.0.0

Permalinks (main @ 9b821bc):

  • iOS:
    NSString *distanceField = [args[@"distance_field"] isKindOfClass:[NSString class]]
    ? args[@"distance_field"]
    : nil;
    if (![vectorFieldName isKindOfClass:[NSString class]] || vectorFieldName.length == 0) {
    if (error) *error = parseError(@"find_nearest requires 'vector_field'");
    return nil;
    }
    if (![vectorValueArray isKindOfClass:[NSArray class]] || vectorValueArray.count == 0) {
    if (error) *error = parseError(@"find_nearest requires non-empty 'vector_value'");
    return nil;
    }
    if (![distanceMeasure isKindOfClass:[NSString class]] || distanceMeasure.length == 0) {
    if (error) *error = parseError(@"find_nearest requires 'distance_measure'");
    return nil;
    }
    __FIRFieldBridge *embeddingField = [[__FIRFieldBridge alloc] initWithName:vectorFieldName];
    NSMutableArray<NSNumber *> *numbers =
    [NSMutableArray arrayWithCapacity:vectorValueArray.count];
    for (id v in vectorValueArray) {
    if ([v isKindOfClass:[NSNumber class]]) {
    [numbers addObject:(NSNumber *)v];
    }
    }
    if (numbers.count != (NSUInteger)vectorValueArray.count) {
    if (error) *error = parseError(@"find_nearest vector_value must be an array of numbers");
    return nil;
    }
    FIRVectorValue *queryVector = [[FIRVectorValue alloc] initWithArray:numbers];
    stage = [[__FIRFindNearestStageBridge alloc] initWithField:embeddingField
    vectorValue:queryVector
    distanceMeasure:distanceMeasure
    limit:limit
    distanceField:distanceField];
  • macOS: https://github.com/firebase/flutterfire/blob/9b821bc18500b9ae55a9cb74b0be949cb64bcd19/packages/cloud_firestore/cloud_firestore/macos/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m#L1578-L1610

The plugin code (7.0.0 / main):

NSString *distanceField = [args[@"distance_field"] isKindOfClass:[NSString class]]
                              ? args[@"distance_field"]
                              : nil;
...
stage = [[__FIRFindNearestStageBridge alloc] initWithField:embeddingField
                                               vectorValue:queryVector
                                           distanceMeasure:distanceMeasure
                                                     limit:limit
                                             distanceField:distanceField];   // NSString *

Why it crashes

  1. The SDK declares the parameter as an expression. firebase-ios-sdk 13.0.0, Firestore/Source/Public/FirebaseFirestore/FIRPipelineBridge.h L179-L185 (12.19.0 has the same declaration, with FIR names instead of __FIR):

    @interface __FIRFindNearestStageBridge : __FIRStageBridge
    - (id)initWithField:(__FIRFieldBridge *)field
            vectorValue:(FIRVectorValue *)vectorValue
        distanceMeasure:(NSString *)distanceMeasure
                  limit:(NSNumber *_Nullable)limit
          distanceField:(__FIRExprBridge *_Nullable)distanceField;

    https://github.com/firebase/firebase-ios-sdk/blob/13.0.0/Firestore/Source/Public/FirebaseFirestore/FIRPipelineBridge.h#L179-L185

  2. The SDK treats the stored value as an expression. Firestore/Source/API/FIRPipelineBridge.mm L839-L842 (13.0.0; the same lines in 12.19.0):

    if (_distanceField) {
      std::shared_ptr<Expr> cpp_distance_field = [_distanceField cppExprWithReader:reader];
      optional_value.emplace(
          std::make_pair(std::string("distance_field"), cpp_distance_field->to_proto()));
    }

    https://github.com/firebase/firebase-ios-sdk/blob/13.0.0/Firestore/Source/API/FIRPipelineBridge.mm#L839-L842

  3. Nothing catches the exception. This runs synchronously from executeWithCompletion: → cppPipelineWithReader: → cppStageWithReader:. Neither the plugin's executePipelineWithFirestore:... nor the Pigeon handler wraps the call in @try.

  4. The SDK's own Swift API wraps the name. It never passes a plain string (Firestore/Swift/Source/Stages.swift L314 @ 13.0.0):

    distanceField: distanceField.map { Field($0).toBridge() } ?? nil
  5. The same parser already does this correctly elsewhere. The unnest branch a few lines above wraps its optional index_field correctly (FLTPipelineParser.m L1567-L1569 @ main):

    __FIRExprBridge *indexFieldExpr =
        (indexFieldStr.length > 0) ? [[__FIRFieldBridge alloc] initWithName:indexFieldStr]
                                   : nil;

A minimal Objective-C program confirms the runtime failure mode. When an NSString stands in for the expression object and receives cppExprWithReader:, it raises:

NSInvalidArgumentException: -[NSTaggedPointerString cppExprWithReader:]: unrecognized selector sent to instance 0x...

Reproducing the issue

Build-time warning: add cloud_firestore (6.3.0 – 7.0.0) to a Flutter app and build for iOS or macOS. The warning above appears for FLTPipelineParser.m.

Runtime: the public Pipeline.findNearest(...) in cloud_firestore has no distanceField parameter today (lib/src/pipeline.dart L418, lib/src/pipeline_stage.dart L177-L208). So the native branch is reached through the public platform interface, which sends the stage map to the native parser unchanged. Add cloud_firestore_platform_interface to pubspec.yaml and run on iOS:

import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart';
import 'package:firebase_core/firebase_core.dart';

Future<void> reproduce() async {
  final firestore = FirebaseFirestorePlatform.instanceFor(
    app: Firebase.app(),
    databaseId: '(default)',
  );
  await firestore
      .pipeline([
        {'stage': 'collection', 'args': {'path': 'items'}},
      ])
      .addStage({
        'stage': 'find_nearest',
        'args': {
          'vector_field': 'embedding',
          'vector_value': [0.1, 0.2, 0.3],
          'distance_measure': 'cosine',
          'distance_field': 'distance',
        },
      })
      .execute();
}
  • Expected: the stage runs, and each result carries the computed distance in distance, as it does on web.
  • Actual (from the code path above): an uncaught NSInvalidArgumentException (-[... cppExprWithReader:]: unrecognized selector) terminates the app.

I traced this by reading the code. The runtime crash has not been run end to end on a device.

Proposed fix

Wrap the field name in the SDK's field expression bridge before passing it, as the unnest branch and the SDK's Swift FindNearest stage already do. The patch below targets main @ 9b821bc, with SDK 13 names. It applies cleanly to both the iOS and macOS copies (checked with git apply --check). Type-checked against the 13.0.0 header with clang -fsyntax-only -Wincompatible-pointer-types, the current call reproduces the warning and the patched call compiles without it.

--- a/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m
+++ b/packages/cloud_firestore/cloud_firestore/ios/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m
@@ -1603,11 +1603,14 @@
           return nil;
         }
         FIRVectorValue *queryVector = [[FIRVectorValue alloc] initWithArray:numbers];
+        __FIRExprBridge *distanceFieldExpr =
+            (distanceField.length > 0) ? [[__FIRFieldBridge alloc] initWithName:distanceField]
+                                       : nil;
         stage = [[__FIRFindNearestStageBridge alloc] initWithField:embeddingField
                                                        vectorValue:queryVector
                                                    distanceMeasure:distanceMeasure
                                                              limit:limit
-                                                     distanceField:distanceField];
+                                                     distanceField:distanceFieldExpr];
       } else {
         if (error)
           *error = parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName]);
--- a/packages/cloud_firestore/cloud_firestore/macos/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m
+++ b/packages/cloud_firestore/cloud_firestore/macos/cloud_firestore/Sources/cloud_firestore/FLTPipelineParser.m
@@ -1603,11 +1603,14 @@
           return nil;
         }
         FIRVectorValue *queryVector = [[FIRVectorValue alloc] initWithArray:numbers];
+        __FIRExprBridge *distanceFieldExpr =
+            (distanceField.length > 0) ? [[__FIRFieldBridge alloc] initWithName:distanceField]
+                                       : nil;
         stage = [[__FIRFindNearestStageBridge alloc] initWithField:embeddingField
                                                        vectorValue:queryVector
                                                    distanceMeasure:distanceMeasure
                                                              limit:limit
-                                                     distanceField:distanceField];
+                                                     distanceField:distanceFieldExpr];
       } else {
         if (error)
           *error = parseError([NSString stringWithFormat:@"Unknown pipeline stage: %@", stageName]);

For a 6.x patch release, the change is the same with the SDK 12 names: FIRExprBridge / FIRFieldBridge / FIRFindNearestStageBridge, at 6.10.0 FLTPipelineParser.m L1565-L1570.

Firebase Core version

firebase_core 4.15.0 (Firebase Apple SDK 12.19.0) with cloud_firestore 6.10.0. The code is unchanged in cloud_firestore 7.0.0 with firebase_core 5.0.0 (Firebase Apple SDK 13.0.0 via Swift Package Manager).

Flutter Version

3.47.5 (stable), Xcode 27.1 (27A9275)

Relevant Log Output

FLTPipelineParser.m:1570:66: warning: incompatible pointer types sending 'NSString *' to parameter of type 'FIRExprBridge * _Nullable' [-Wincompatible-pointer-types]

Flutter dependencies

cloud_firestore: 6.10.0, firebase_core: 4.15.0. Also checked against cloud_firestore: 7.0.0 / firebase_core: 5.0.0 and main.

Additional context and comments

Impact

  • Build: every iOS/macOS build of cloud_firestore 6.3.0 – 7.0.0 shows this warning. Projects that build with warnings as errors fail.
  • Run time today: low. The crash needs distance_field to reach the Apple parser, and the public cloud_firestore Dart API does not send it yet. Only callers that go through the platform interface can hit it.
  • Run time after a Dart change: when Pipeline.findNearest gains a distanceField option, every Apple app that uses it will crash. Web already supports this option.

Platform parity (related)

The distance_field argument is handled differently on each platform:

Platform Handling of distance_field
Web supported: cloud_firestore_web/lib/src/pipeline_expression_parser_web.dart L755-L767
Apple read, but with the wrong type (this issue)
Android ignored: PipelineStageHandlers.java L308-L338
Dart Pipeline.findNearest does not expose it

Related PR

#18607 (open; migrates the Apple plugin to Swift) replaces this file. Its PipelineParser.swift wraps the name correctly (distanceField.map { FieldBridge(name: $0) }), so merging it would also fix this. It currently has merge conflicts and predates the SDK 13 __ renames from #18783. Until it lands, the three-line fix above removes both the warning and the crash path.

Activity

  1. SelaseKay commented on Oct 21, 2026

    @SelaseKay
    Contributor

    Hi @DebuggerTr-Consulting, thanks for the report. I'm looking into this.

  2. self-assigned this
    on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Needs AttentionThis issue needs maintainer attention.platform: iosIssues / PRs which are specifically for iOS.platform: macosIssues / PRs which are specifically for MacOS.plugin: cloud_firestoretype: bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions