Skip to content

flutterGetDatadogContext FFI symbol stripped by linker in static framework builds — breaks distributed tracing #979

@MelonMania

Description

@MelonMania

Description

The flutterGetDatadogContext FFI symbol is correctly implemented in Swift (DatadogSdkPlugin.swift:20-36) with @_cdecl, but gets dead-stripped by the linker when the plugin is built as a static framework. This causes distributed tracing to silently fail in both datadog_dio and datadog_tracking_http_client.

Environment

  • datadog_flutter_plugin: 3.1.0
  • datadog_dio: 2.1.0
  • datadog_tracking_http_client: 3.1.0
  • datadog_session_replay: 1.0.0-preview.10
  • Flutter: 3.32.5 / Dart: 3.8.1
  • Platform: iOS (CocoaPods with use_frameworks!)

Steps to Reproduce

  1. Setup Datadog SDK with firstPartyHostsWithTracingHeaders configured
  2. Use datadog_dio or datadog_tracking_http_client for distributed tracing
  3. Make an HTTP request
  4. Observe: trace headers (x-datadog-trace-id, traceparent) are NOT injected

Error

When calling generateTracingContext() which calls sdk.platform.getContext()_getDatadogContext() FFI:

Invalid argument(s): Couldn't resolve native function 'flutterGetDatadogContext'

This error is silently caught by datadog_dio's _trackRequest try-catch, making it invisible unless explicitly debugged.

Root Cause Analysis

The @ffi.Native annotation in ios_platform_bridge.dart:51-53:

@ffi.Native<_DatadogCContext Function()>(
    isLeaf: true, symbol: 'flutterGetDatadogContext')
external _DatadogCContext _getDatadogContext();

Has no assetId parameter and no @DefaultAsset library annotation, so Dart falls back to dlsym(RTLD_DEFAULT, "flutterGetDatadogContext") for process-level symbol lookup.

Meanwhile, the podspec sets s.static_framework = true. Combined with use_frameworks! in the Podfile, this produces a static framework. The linker extracts object files from the static archive and only includes those needed to resolve static references.

Since flutterGetDatadogContext is a module-level free C function (not an ObjC class member), the -ObjC linker flag does NOT force its inclusion. The linker dead-strips it because nothing statically references it.

Evidence:

  • Symbol exists in compiled object: nm DatadogSdkPlugin.o | grep flutterGetT _flutterGetDatadogContext
  • Symbol exists in static archive: nm Binary/datadog_flutter_plugin | grep flutterGetT _flutterGetDatadogContext
  • Symbol NOT found at runtime via dlsym(RTLD_DEFAULT, ...) → stripped during final linking

Suggested Fix: Add assetId to the @ffi.Native annotation

@ffi.Native<_DatadogCContext Function()>(
    isLeaf: true, 
    symbol: 'flutterGetDatadogContext',
    assetId: 'datadog_flutter_plugin')  // explicit asset resolution
external _DatadogCContext _getDatadogContext();

Workaround

Create a custom Dio interceptor that bypasses the FFI by using TracingId.traceId() / TracingId.spanId() (pure Dart) to generate TracingContext directly, then use injectTracingHeaders() and rum.startResource() / rum.stopResource() for manual resource tracking. This avoids the getContext() FFI call entirely.

Impact

  • All distributed tracing is broken (trace headers not injected)
  • datadog_dio and datadog_tracking_http_client silently fail
  • HTTP resources may not be tracked depending on configuration
  • Affects all iOS builds using use_frameworks! with the default static_framework = true

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions