Skip to content

feat: add Linux desktop support to core, auth, firestore, database, storage, remote_config, app_check, functions#18461

Closed
akshaynexus wants to merge 24 commits into
firebase:mainfrom
akshaynexus:add-linux-support
Closed

feat: add Linux desktop support to core, auth, firestore, database, storage, remote_config, app_check, functions#18461
akshaynexus wants to merge 24 commits into
firebase:mainfrom
akshaynexus:add-linux-support

Conversation

@akshaynexus

@akshaynexus akshaynexus commented Jul 18, 2026

Copy link
Copy Markdown

Description

Adds Linux desktop support to eight FlutterFire plugins, mirroring the existing Windows desktop implementation (same Firebase C++ SDK 13.5.0, same development-only support policy):

firebase_core, firebase_auth, cloud_firestore, firebase_database, firebase_storage, firebase_remote_config, firebase_app_check, cloud_functions

These are exactly the products the Firebase C++ SDK provides real (non-stub) desktop implementations for.

What was added

  • linux/ implementation per plugin targeting Flutter's GLib/GObject Linux embedder (FlPluginRegistrar, FlValue, FlMethodChannel, FlEventChannel) — ported from the windows/ C++ implementations. 125 Pigeon host-API methods and 14 event channels (auth state / ID token, Firestore document & query snapshots + snapshots-in-sync, Database value/child listeners, Storage task progress, Remote Config update listener, App Check token changes).
  • Cloud Functions on Linux (cloud_functions): a new linux/ GObject plugin backed by the Firebase C++ SDK firebase::functions module (added to the shared --start-group link in firebase_core), implementing CloudFunctionsHostApi.call via Functions::GetHttpsCallable[FromURL]() + HttpsCallableReference::Call(), with FlValuefirebase::Variant marshalling and canonical Cloud Functions error-code mapping. The HttpsCallableReference is heap-allocated and freed only after the response is delivered, because its internal owns the curl transport that runs the request on an SDK background thread — letting it go out of scope when the handler returns destroys the transport mid-request (use-after-free). Unary callables work; streaming callables (registerEventChannel) respond unimplemented.
  • Pigeon codegen, not hand-written channels: each *_platform_interface/pigeons/messages.dart gained gobjectHeaderOut/gobjectSourceOut outputs, so the Linux channel layer is generated from the same single source of truth as every other platform. A melos generate:pigeon:linux script (scripts/generate_pigeon_linux.sh) re-applies the small mechanical post-gen transforms the GObject generator needs (reserved-word deletedelete_ for core and auth, dispatch-local handle rename for storage, Firestore custom-codec delegation) so a raw dart run pigeon can't silently regress them.
  • firebase_core codec fix: the Linux Pigeon codec was regenerated to include the recaptchaSiteKey field (the 15th CoreFirebaseOptions field the current firebase_core_platform_interface encodes). The previously-committed 14-field Linux codec threw RangeError (0..13: 14) on Firebase.initializeApp() when the Dart side decoded the app options back.
  • CMake / SDK download modeled on the Windows CMakeLists: same FIREBASE_SDK_VERSION (13.5.0), same FIREBASE_CPP_SDK_DIR env override with version check. Linux downloads the full firebase_cpp_sdk_13.5.0.zip (Google publishes no Linux-only archive); the SDK's own CMake selects the correct x86_64 C++11-ABI static libraries.
  • Link architecture (the part that differs from Windows by necessity): the prebuilt Firebase static archives have circular symbol dependencies (product libs → firebase::rest::* inside libfirebase_app.a; libfirebase_firestore.afirebase::g_auth_initializer inside libfirebase_auth.a). GNU ld resolves archives left-to-right (unlike MSVC), so firebase_core_plugin owns the SDK link and wraps all product archives (including libfirebase_functions.a) in a single -Wl,--start-group/--end-group, plus libsecret-1 (forwarded from the SDK's own firebase_auth interface requirement), system OpenSSL (needed by Database/Firestore archives), and pthread. Product plugins link only firebase_core_plugin. Plugins are built as STATIC libraries (as on Windows) so all plugins share one firebase::App registry in the runner.
  • Cross-plugin constants registry: FlValue-based FlutterFirebasePluginRegistry in firebase_core, mirroring the Windows/Android registry, so initializeCore aggregates each plugin's constants.
  • Repo integration: Linux column in the root README support matrix (marked development-only, same (*) policy as Windows), linux/ runners for each plugin example and for the tests/ app, and a build-only Linux CI workflow (.github/workflows/linux.yaml, ubuntu-latest) that builds the firebase_core example.
  • Where the Windows implementation has TODO stubs for APIs the C++ SDK does support, the Linux port implements them (e.g. FirebaseAppHostApi.delete destroys the firebase::App instance); where the SDK genuinely lacks the API, the Windows no-op is mirrored with the same comment.

What was tested (real hardware)

All builds were verified on physical Linux hardware (CachyOS, Flutter 3.44.4 stable, CMake 4.4, GCC/Clang toolchain), not just cross-checked statically:

  • flutter build linux succeeds for all eight plugin example apps, including the full SDK zip download/extract path in firebase_core's CMake and the FIREBASE_CPP_SDK_DIR override path for the rest.
  • flutter build linux succeeds for the tests/ app, which links six FlutterFire plugins into one binary (core, auth, app_check, database, remote_config, storage) — verifying the plugins coexist and the group-link strategy holds under multi-plugin aggregation.
  • Cloud Functions verified end-to-end: an authenticated httpsCallable().call() round-trips against a deployed callable and returns its data (exercised in a downstream production app to mint Supabase auth claims immediately after Firebase login).
  • Symbol-level verification with nm on the produced binaries: every <plugin>_plugin_register_with_registrar entry point present, product SDK symbols linked (e.g. 1,166 firebase::auth::* symbols in the auth example binary).
  • Launch smoke test: binaries start and reach GTK initialization (headless SSH session, so no display was attached).
  • flutter analyze clean on the touched Dart surfaces.

What works / known limitations

  • Works: app initialization & multi-app, Auth flows over the C++ desktop SDK, Firestore (queries, snapshots listeners, transactions, custom codec for Firestore types), Realtime Database (listeners, transactions), Storage (upload/download tasks with progress events), Remote Config, App Check host APIs, and Cloud Functions HTTPS callables (unary call()).
  • Development-only, exactly like Windows: the Firebase C++ desktop SDK is a beta/dev workflow, so the README marks Linux with the same (*) footnote.
  • Limitations deliberately mirrored from Windows for parity: Remote Config realtime-update callbacks never fire (desktop SDK limitation), setCustomSignals responds unimplemented, Database setPersistenceCacheSizeBytes/emulator wiring no-ops, Firestore VectorValue wire type not decoded natively, Auth MFA/TOTP host APIs generated but not registered (Dart receives channel-not-implemented, as on Windows).
  • Cloud Functions streaming callables (httpsCallable().stream()registerEventChannel) are not yet implemented on Linux and respond unimplemented; unary call() works.
  • E2E test wiring for a Linux runner (emulator jobs mirroring windows.yaml) is left as follow-up; the added CI job is build-only.
  • The auth example's font_awesome_flutter/flutter_signin_button pair is bumped to the latest versions (fa 10 does not compile on Flutter ≥ 3.44 where IconData is final). No released flutter_signin_button supports fa 11 yet, so the workspace root carries a temporary dependency_overrides onto a patched fork; fix: support font_awesome_flutter 11 (FaIconData is no longer an IconData) ZaynJarvis/Flutter-Sign-in-Button#125 has been opened upstream to make that override removable. If maintainers prefer, this dep bump can be split into a separate PR.

Related Issues

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]).
This will ensure a smooth and quick review process. Updating the pubspec.yaml and changelogs is not required.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide). — Native Linux behavior is verified by building and symbol-checking all eight examples plus the multi-plugin tests/ app on real Linux hardware; no Dart behavior changed (the Dart/Pigeon layer is shared with existing platforms and covered by existing tests). Happy to add Linux e2e wiring in a follow-up.
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (melos run analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@google-cla

google-cla Bot commented Jul 18, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@Lyokone

Lyokone commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Hey, first of all thanks for this PR.
Huge push.
I don't have a Linux environment to test right now, and since it's a big PR, it'll take a bit longer to get reviewed.
You mentioned mirroring the Windows implementation, is there some files that we can share directly between both? Allowing for a symbolic link instead of copying the file?

@Lyokone

Lyokone commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Couple of CI failing here, you'll be nice to merge with main and then make sure that the CI goes green :)

…tabase, storage, remote_config, app_check

Ports the Windows C++ (firebase-cpp-sdk 13.5.0) plugin implementations to
the Flutter Linux GObject embedder for 7 packages:

- Pigeon GObject codegen (gobjectHeaderOut/SourceOut) from the existing
  single-source pigeons/messages.dart definitions
- linux/CMakeLists.txt per package: downloads the full firebase_cpp_sdk
  zip (contains prebuilt Linux x86_64 libs, both ABI variants), honors
  FIREBASE_CPP_SDK_DIR override, STATIC plugin libs to share one
  firebase_app registry across plugins
- FlValue-based cross-plugin constants registry in firebase_core
- All 123 host-API methods and 14 event channels (auth state/id-token,
  firestore snapshots, database value/child events, storage task
  progress, remote-config updates, app-check token changes)
- Linux implements delete() and other gaps Windows leaves as TODO stubs
- Shared platform-agnostic storage helpers extracted to src/ used by
  both windows/ and linux/
- README Linux column, melos generate:pigeon:linux + regen script
  encoding the required post-gen transforms, build-only Linux CI job

Linux support is development-only, mirroring the Windows desktop policy.
…K link owned by firebase_core

The prebuilt Firebase C++ archives have circular symbol deps
(product libs -> firebase::rest::* in libfirebase_app.a;
libfirebase_firestore.a -> firebase::g_auth_initializer in
libfirebase_auth.a) that an ordered per-plugin link line cannot satisfy.
firebase_core_plugin now links every desktop product archive in one
-Wl,--start-group/--end-group and forwards libsecret (from the SDK's
firebase_auth interface) plus system OpenSSL needed by database/firestore.
Product plugins link only firebase_core_plugin.
…op unused font_awesome_flutter from auth example

The SDK include dir was INTERFACE-only, but since the imported SDK targets
are no longer linked directly, firebase_core's own sources need it too.
font_awesome_flutter 10.x is incompatible with Flutter 3.44 (final
IconData) and was never imported by the auth example.
v10 extends IconData, which is a final class as of Flutter 3.44, breaking
compilation. flutter_signin_button constrains font_awesome_flutter with
'>=9.0.0' so v11 resolves cleanly.
Enables building/running the E2E tests app on Linux desktop; exercises
the firebase_auth Linux plugin (no example app covers it) alongside
core, app_check, database, remote_config, and storage.
… 2.1.1 in example

font_awesome_flutter 11 is required for Flutter >=3.44 (IconData became
final; fa 10 subclassed it). No released flutter_signin_button supports
fa 11's FaIconData yet, so override it with a patched fork (fa11-compat)
until upstream ships the fix. Overrides live in the workspace root, as
pub workspaces ignore member-level dependency_overrides.
Add the permissions block (contents: read) and use the same pinned
action SHAs as the other workflows.
@akshaynexus

Copy link
Copy Markdown
Author

I looked at making Linux a wrapper over the Windows code , it's not directly possible today because the Windows impls thread EncodableValue/pigeon types through the logic rather than keeping them at the edges. Getting there means first refactoring Windows into a platform-neutral core with thin codec adapters on both sides. That would deduplicate ~3.5–4k lines of firebase-cpp logic, but ~75% of this PR's diff is generated pigeon output (GObject codegen, can't be shared with the C++ codegen) plus runner/CMake scaffolding, so the PR wouldn't get meaningfully smaller and it would add a risky refactor of 7 shipped Windows plugins to an already-large review. I've started the pattern where it was cheap (firebase_storage/src/ is compiled into both platforms) and I'd prefer to do the full core-extraction as a follow-up PR where the Windows changes can be reviewed on their own.

hope this answers your question

…iteKey

The Linux firebase_core Pigeon (GObject) sources were stale: they were
generated before `recaptchaSiteKey` was added to CoreFirebaseOptions, so
the codec only emitted 14 fields. The published Dart platform interface
(firebase_core_platform_interface 8.0.0) decodes 15 fields, reading
`recaptchaSiteKey` at index 14. After Firebase.initializeApp() the native
side encoded the app options back with only 14 elements, so the Dart
decoder threw on Linux desktop:

  RangeError (length): Invalid value: Not in inclusive range 0..13: 14

Regenerate messages.g.{h,cc} with pigeon 26.3.4 (adds the recaptcha_site_key
field to the struct, constructor, getter, encode, decode, equals and hash),
and extend scripts/generate_pigeon_linux.sh to also apply the `delete` ->
`delete_` C++ keyword rename to firebase_core (it previously only patched
firebase_auth, so a clean regen of firebase_core did not compile). The
Linux codec now matches the 15-field Dart Pigeon and Firebase initializes
on Linux desktop.
cloud_functions had no Linux implementation, so httpsCallable().call()
threw MissingPluginException ("Unable to establish connection on channel
... CloudFunctionsHostApi.call") on Linux desktop, breaking any flow that
invokes a callable (e.g. minting Supabase auth claims post-login).

Add a GObject plugin backed by the Firebase C++ SDK functions module
(already linked into the runner via firebase_core's --start-group), mirroring
the firebase_database / firebase_storage Linux plugins:

- pigeons/messages.dart: add gobjectHeaderOut/gobjectSourceOut so the Linux
  codec is generated alongside the other platforms.
- linux/messages.g.{h,cc}: generated Pigeon GObject codec.
- linux/cloud_functions_plugin.cc: implements CloudFunctionsHostApi.call via
  Functions::GetHttpsCallable[FromURL]() + HttpsCallableReference::Call(),
  with FlValue<->firebase::Variant conversion and canonical error-code
  mapping. registerEventChannel (streaming callables) returns "unimplemented".
- linux/CMakeLists.txt, plugin_version.h.in, plugin header, and the pubspec
  linux pluginClass registration.

The HttpsCallableReference is heap-allocated and freed only after the
response is delivered on the main thread: its internal owns the curl
transport that runs the request on an SDK background thread, so destroying
it when the handler returns (as a local would) crashes with a use-after-free
in the curl thread. Verified end to end: login + Supabase claim sync now
succeed on Linux.
@akshaynexus akshaynexus changed the title feat: add Linux desktop support to core, auth, firestore, database, storage, remote_config, app_check feat: add Linux desktop support to core, auth, firestore, database, storage, remote_config, app_check, functions Jul 20, 2026
@akshaynexus

Copy link
Copy Markdown
Author

Added cloud functions support for linux, tested on a app using firebase auth,cloud functions and other libs and works on linux desktop

@Lyokone

Lyokone commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Thanks for the explanation. Could you also update the e2e files so we have tests running on the linux platform as well?

@akshaynexus

Copy link
Copy Markdown
Author

Sure will do

- add TargetPlatform.linux case to tests e2e entrypoint covering the
  plugins with Linux implementations (core, auth, database, functions,
  remote_config, storage, app_check)
- generalise the desktop C++ SDK guards: shared isDesktopCppSdk helper
  replaces the TargetPlatform.windows checks since Windows and Linux
  share the same firebase-cpp-sdk limitations
- map Linux to the android FirebaseOptions in the firestore example
  integration tests
- turn linux.yaml into an e2e-linux workflow mirroring e2e-windows/
  e2e-macOS: builds the tests app and the cloud_firestore example and
  runs their integration tests against the Firebase emulator under xvfb
- map Linux to the android FirebaseOptions in the tests app (desktop C++
  SDK convention, same as Windows)
- extend the desktop C++ SDK guards (Platform.isWindows and listing
  skips) to Linux via the shared isDesktopCppSdk helper
- exclude firebase_database: the C++ SDK has no UseEmulator API for
  Realtime Database, so the suite would hit production and hang
- drain the uncancellable timed-out functions call on desktop so the
  following test is not queued behind it
- run the storage unsupported-listing assertion on Linux with a
  platform-aware message
- use melos 5.3.0 in the linux workflow to match the other workflows
…nted listing

The C++ SDK exposes no per-call deadline API, so the functions plugin
now enforces the Dart-provided timeout with a GLib timer and responds
deadline-exceeded while the SDK completion only cleans up. The storage
plugin reported silently-empty results for list/listAll, which the C++
SDK does not support; it now fails with 'unimplemented' exactly like the
Windows implementation.
FieldValueToFlValue returned blobs as a plain Uint8List, so the Dart
FirestoreMessageCodec never reconstructed a `Blob` (tag 183). Box them
as a DATA_TYPE_BLOB custom value and write the tag + size + bytes wire
format the Dart codec expects.
@akshaynexus

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Linux support for several Firebase plugins, including cloud_firestore, cloud_functions, firebase_app_check, firebase_auth, firebase_core, and firebase_database. The changes involve updating build configurations, adding platform-specific implementations, and adjusting integration tests to support Linux desktop environments. My review identified a missing update in the README table for cloud_functions and a consistent issue with future-dated copyright headers across the new files.

Comment thread README.md Outdated
akshaynexus and others added 2 commits July 22, 2026 00:43
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
The C++ SDK already caches instances per (app, region) inside
Functions::GetInstance; a plugin-side raw-pointer cache could dangle if
an app were deleted and recreated. Ports the same fix as the Windows
implementation review.
@akshaynexus

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Linux desktop support for several Firebase plugins, including Cloud Firestore, Cloud Functions, Firebase App Check, Firebase Auth, Firebase Core, and Firebase Database, by adding GObject/C++ implementations, CMake configurations, and updating integration tests. The reviewer's feedback recommends replacing standard console output (std::cout) with GLib's native g_warning logging for unsupported aggregation features in the Firestore plugin to ensure better visibility and platform consistency.

Comment thread packages/cloud_firestore/cloud_firestore/linux/cloud_firestore_plugin.cc Outdated
Comment thread packages/cloud_firestore/cloud_firestore/linux/cloud_firestore_plugin.cc Outdated
…rnings

std::cout is not visible in release builds; g_warning is the idiomatic
GLib logging call. Addresses review feedback.
@akshaynexus

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request adds Linux support for the Firebase core, auth, database, app check, and cloud functions plugins. It includes the necessary CMake configuration, platform-specific implementation files, and updates to the example apps to support Linux. I have removed the comments regarding copyright year updates as they are non-code changes and do not require action.

Comment thread packages/cloud_firestore/cloud_firestore/linux/cloud_firestore_plugin.cc Outdated
@akshaynexus

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Linux platform support for several Firebase plugins—including Cloud Firestore, Cloud Functions, App Check, Auth, Core, and Database—using the Firebase C++ SDK, alongside updated integration tests and example applications. The code review identified several critical and high-severity issues across these new implementations: a data race on the global transactions_ map in Firestore, potential null pointer dereferences when retrieving Firebase App instances, and a resource leak in the Database plugin due to missing registration with the core plugin registry. Additionally, multiple memory leaks were flagged across the plugins where heap-allocated objects or GLib idle sources could leak if cancelled or destroyed before execution.

Comment thread packages/firebase_auth/firebase_auth/linux/firebase_auth_plugin.cc
- firestore: guard the cross-thread transactions_ map with a mutex,
  abort (not commit) transactions whose Dart stream was cancelled, and
  null-check App::GetInstance before creating a Firestore instance
- database: register with the FlutterFirebasePlugin registry so hot
  restart tears down listeners/connections and drops cached instances
- all plugins: post main-thread closures with g_idle_add_full and a
  destroy-notify so pending closures are freed even if the source is
  destroyed without running (main loop teardown); app_check event and
  token-response payloads get the same treatment
@akshaynexus

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Linux desktop support for several Firebase plugins, including Cloud Firestore, Cloud Functions, Firebase App Check, Firebase Auth, Firebase Core, and Firebase Database, by adding CMake configurations, GObject-based Pigeon bindings, and C++ wrappers around the Firebase C++ SDK. The code review identified several critical memory management and resource cleanup issues across these new implementations. Specifically, there are potential memory leaks and dangling pointers due to heap-allocated structs captured in asynchronous callbacks or passed to GLib idle sources without proper destroy-notify handlers. Additionally, several static maps and event channels are not cleared upon plugin disposal, which could leak GObjects and leave dangling SDK listeners during hot restarts. Finally, the reviewer advised against using std::atexit for database cleanup to avoid static destruction order crashes, recommending that cleanup be handled in the plugin's dispose method instead.

Comment on lines +270 to +308
struct CallState {
CloudFunctionsCloudFunctionsHostApiResponseHandle* response_handle;
HttpsCallableReference* ref;
bool responded = false;
guint timeout_source_id = 0;
};
auto* state = new CallState{response_handle, ref};

g_object_ref(response_handle);

int64_t timeout_ms = 0;
FlValue* timeout_value = fl_value_lookup_string(arguments, "timeout");
if (timeout_value != nullptr &&
fl_value_get_type(timeout_value) == FL_VALUE_TYPE_INT) {
timeout_ms = fl_value_get_int(timeout_value);
}
if (timeout_ms > 0) {
state->timeout_source_id = g_timeout_add(
static_cast<guint>(timeout_ms),
+[](gpointer user_data) -> gboolean {
auto* timeout_state = static_cast<CallState*>(user_data);
timeout_state->timeout_source_id = 0;
if (!timeout_state->responded) {
timeout_state->responded = true;
FlValue* details = fl_value_new_map();
fl_value_set_string_take(details, "code",
fl_value_new_string("deadline-exceeded"));
fl_value_set_string_take(
details, "message",
fl_value_new_string("The operation timed out."));
cloud_functions_cloud_functions_host_api_respond_error_call(
timeout_state->response_handle, "deadline-exceeded",
"The operation timed out.", details);
fl_value_unref(details);
}
return G_SOURCE_REMOVE;
},
state);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-critical critical

The heap-allocated CallState raw pointer is captured by value in the OnCompletion lambda. If the lambda is destroyed without running (e.g., if the Future is cancelled or the App is shut down), state and its internal ref and response_handle are leaked. Additionally, if the CallState is destroyed before the timeout fires, the timeout callback will receive a dangling pointer. Refactoring CallState to use std::shared_ptr with a destructor that cleans up resources and removes the timeout source prevents these memory leaks and use-after-free bugs.

  struct CallState {
    ~CallState() {
      if (timeout_source_id != 0) {
        g_source_remove(timeout_source_id);
      }
      if (ref != nullptr) {
        delete ref;
      }
      if (response_handle != nullptr) {
        g_object_unref(response_handle);
      }
    }
    CloudFunctionsCloudFunctionsHostApiResponseHandle* response_handle;
    HttpsCallableReference* ref;
    bool responded = false;
    guint timeout_source_id = 0;
  };
  auto state = std::make_shared<CallState>();
  state->response_handle = response_handle;
  g_object_ref(response_handle);
  state->ref = ref;

  int64_t timeout_ms = 0;
  FlValue* timeout_value = fl_value_lookup_string(arguments, "timeout");
  if (timeout_value != nullptr &&
      fl_value_get_type(timeout_value) == FL_VALUE_TYPE_INT) {
    timeout_ms = fl_value_get_int(timeout_value);
  }
  if (timeout_ms > 0) {
    state->timeout_source_id = g_timeout_add(
        static_cast<guint>(timeout_ms),
        +[](gpointer user_data) -> gboolean {
          auto* timeout_state = static_cast<CallState*>(user_data);
          timeout_state->timeout_source_id = 0;
          if (!timeout_state->responded) {
            timeout_state->responded = true;
            FlValue* details = fl_value_new_map();
            fl_value_set_string_take(details, "code",
                                     fl_value_new_string("deadline-exceeded"));
            fl_value_set_string_take(
                details, "message",
                fl_value_new_string("The operation timed out."));
            cloud_functions_cloud_functions_host_api_respond_error_call(
                timeout_state->response_handle, "deadline-exceeded",
                "The operation timed out.", details);
            fl_value_unref(details);
          }
          return G_SOURCE_REMOVE;
        },
        state.get());
  }

Comment on lines +105 to +119
void SendSuccessOnPlatformThread(std::shared_ptr<EventChannelState> state,
FlValue* value /* transfer full */) {
if (!state) {
if (value != nullptr) fl_value_unref(value);
return;
}

PostToMainThread([state, value]() {
std::lock_guard<std::mutex> lock(state->mutex);
if (state->active && state->channel != nullptr) {
fl_event_channel_send(state->channel, value, nullptr, nullptr);
}
if (value != nullptr) fl_value_unref(value);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If the main-thread task dispatched via PostToMainThread is destroyed without running (e.g., during engine teardown or hot restart), the raw FlValue* will be leaked because fl_value_unref is only called inside the lambda body. Wrapping the raw pointer in a std::shared_ptr with a custom deleter ensures that the reference is safely released when the lambda is destroyed.

void SendSuccessOnPlatformThread(std::shared_ptr<EventChannelState> state,
                                 FlValue* value /* transfer full */) {
  if (!state) {
    if (value != nullptr) fl_value_unref(value);
    return;
  }

  std::shared_ptr<FlValue> shared_value(value, [](FlValue* v) {
    if (v != nullptr) fl_value_unref(v);
  });

  PostToMainThread([state, shared_value]() {
    std::lock_guard<std::mutex> lock(state->mutex);
    if (state->active && state->channel != nullptr) {
      fl_event_channel_send(state->channel, shared_value.get(), nullptr, nullptr);
    }
  });
}

Comment on lines +1971 to +1973
static void cloud_firestore_plugin_dispose(GObject* object) {
G_OBJECT_CLASS(cloud_firestore_plugin_parent_class)->dispose(object);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The file-level static maps stream_handlers_ and event_channels_ are never cleared when the plugin is disposed. This leads to memory leaks of the FlEventChannel objects and dangling listeners registered on the Firestore SDK. Implement proper cleanup in cloud_firestore_plugin_dispose.

static void cloud_firestore_plugin_dispose(GObject* object) {
  stream_handlers_.clear();
  for (auto& pair : event_channels_) {
    if (pair.second != nullptr) {
      g_object_unref(pair.second);
    }
  }
  event_channels_.clear();
  G_OBJECT_CLASS(cloud_firestore_plugin_parent_class)->dispose(object);
}

Comment on lines +114 to +118
void OnAppCheckTokenChanged(const AppCheckToken& token) override {
TokenEvent* event =
new TokenEvent{FL_EVENT_CHANNEL(g_object_ref(channel_)), token.token};
g_idle_add(SendTokenEvent, event);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The TokenEvent raw pointer is allocated on the heap and passed to g_idle_add. If the idle source is destroyed without running, the TokenEvent struct and its internal event->channel reference are leaked. Using g_idle_add_full with a destroy notify callback ensures that the resources are safely released in all scenarios.

  void OnAppCheckTokenChanged(const AppCheckToken& token) override {
    TokenEvent* event =
        new TokenEvent{FL_EVENT_CHANNEL(g_object_ref(channel_)), token.token};
    g_idle_add_full(
        G_PRIORITY_DEFAULT,
        [](gpointer data) -> gboolean {
          TokenEvent* event = static_cast<TokenEvent*>(data);
          g_autoptr(FlValue) map = fl_value_new_map();
          fl_value_set_string_take(map, "token",
                                   fl_value_new_string(event->token.c_str()));
          g_autoptr(GError) error = nullptr;
          if (!fl_event_channel_send(event->channel, map, nullptr, &error)) {
            g_warning("firebase_app_check: failed to send token event: %s",
                      error->message);
          }
          return G_SOURCE_REMOVE;
        },
        event,
        [](gpointer data) {
          TokenEvent* event = static_cast<TokenEvent*>(data);
          g_object_unref(event->channel);
          delete event;
        });
  }

Comment on lines +537 to +542
static std::string SetupAuthEventChannel(Auth* auth, const std::string& name,
bool is_id_token_channel) {
static std::unordered_map<std::string, AuthEventChannel*>* channels =
new std::unordered_map<std::string, AuthEventChannel*>();

auto existing = channels->find(name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The channels map is defined as a local static pointer and is never cleaned up. This leaks the FlEventChannel objects and leaves the SDK listeners registered on the Auth instance, which can cause crashes or memory leaks on hot restart or engine teardown. Defining auth_event_channels_ as a file-level static map allows proper cleanup in firebase_auth_plugin_dispose.

static std::string SetupAuthEventChannel(Auth* auth, const std::string& name,
                                         bool is_id_token_channel) {
  auto existing = auth_event_channels_.find(name);
  if (existing != auth_event_channels_.end()) {
    if (existing->second->messenger == g_binary_messenger) {
      return name;
    }
    AuthEventChannel* stale = existing->second;
    if (stale->id_token_listener != nullptr) {
      stale->auth->RemoveIdTokenListener(stale->id_token_listener);
      stale->id_token_listener = nullptr;
    }
    if (stale->auth_state_listener != nullptr) {
      stale->auth->RemoveAuthStateListener(stale->auth_state_listener);
      stale->auth_state_listener = nullptr;
    }
    auth_event_channels_.erase(existing);
  }

  auto* event_channel = new AuthEventChannel();
  event_channel->messenger = g_binary_messenger;
  event_channel->auth = auth;
  event_channel->is_id_token_channel = is_id_token_channel;

  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  event_channel->channel = fl_event_channel_new(
      g_binary_messenger, name.c_str(), FL_METHOD_CODEC(codec));
  fl_event_channel_set_stream_handlers(
      event_channel->channel, AuthEventChannelListenCb,
      AuthEventChannelCancelCb, event_channel, nullptr);

  auth_event_channels_[name] = event_channel;
  return name;
}

Comment on lines +1455 to +1457
static void firebase_auth_plugin_dispose(GObject* object) {
G_OBJECT_CLASS(firebase_auth_plugin_parent_class)->dispose(object);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The auth_event_channels_ map is never cleared when the plugin is disposed. Implement proper cleanup in firebase_auth_plugin_dispose to unregister SDK listeners and unref the FlEventChannel objects.

static void firebase_auth_plugin_dispose(GObject* object) {
  for (auto& [name, event_channel] : auth_event_channels_) {
    if (event_channel->id_token_listener != nullptr) {
      event_channel->auth->RemoveIdTokenListener(event_channel->id_token_listener);
      delete event_channel->id_token_listener;
    }
    if (event_channel->auth_state_listener != nullptr) {
      event_channel->auth->RemoveAuthStateListener(event_channel->auth_state_listener);
      delete event_channel->auth_state_listener;
    }
    g_object_unref(event_channel->channel);
    delete event_channel;
  }
  auth_event_channels_.clear();
  G_OBJECT_CLASS(firebase_auth_plugin_parent_class)->dispose(object);
}

Comment on lines +1351 to +1353
static void firebase_database_plugin_dispose(GObject* object) {
G_OBJECT_CLASS(firebase_database_plugin_parent_class)->dispose(object);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Implement proper cleanup in firebase_database_plugin_dispose to clear database_instances_ and trigger CleanupBeforeStaticDestruction on plugin disposal.

static void firebase_database_plugin_dispose(GObject* object) {
  CleanupBeforeStaticDestruction();
  database_instances_.clear();
  G_OBJECT_CLASS(firebase_database_plugin_parent_class)->dispose(object);
}

Comment on lines +1381 to +1383
// Register atexit handler to clean up listeners and disconnect
// before static destruction triggers thread joins in the C++ SDK.
std::atexit(CleanupBeforeStaticDestruction);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Registering std::atexit handlers in a dynamic plugin environment can lead to static destruction order issues or crashes if the library is unloaded. Instead, perform the cleanup in firebase_database_plugin_dispose.

@Lyokone

Lyokone commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Hello @akshaynexus

Thank you for the significant amount of work and effort you’ve put into adding Linux support.

Unfortunately, Linux support is not currently on the FlutterFire roadmap. Accepting this PR would commit the maintainers to reviewing, testing, releasing, and supporting Linux across the affected plugins long-term. Given the size of the change and our current priorities, we cannot review it to the standard required or take on that maintenance commitment, so we’re going to close the PR.

Thank you again for the contribution and for helping explore what Linux support could look like.

@Lyokone Lyokone closed this Jul 23, 2026
@Lyokone Lyokone added the resolution: wontfix This will not be worked on label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

resolution: wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants