Skip to content

[Due for payment 2026-06-12] [Due for payment 2026-06-04] [$250] [Sentry: APP-8KQ] iOS Fatal C++ Exception: St14overflow_error #91225

Closed

Description

@mountiny

Sentry

https://expensify.sentry.io/issues/APP-8KQ

Impact (snapshot at filing)

  • Users (total since first seen): 14,139
  • Events: 15,626
  • Users (last 14d): ~254
  • First seen: 2026-03-27
  • Last seen: now (ongoing)
  • Platform: iOS, hybrid_app build (HybridApp / OldDot)
  • App version(s): seen on 9.3.77 and earlier
  • OS: iOS 26.x
  • Mechanism: `cpp_exception`, fatal

Stack trace

Sentry reports `No stacktrace available` — only the top-level C++ exception type is captured. Mechanism is `cpp_exception`, fatal level, with no symbolicated frames.

Suspected cause

Unknown. `std::overflow_error` is thrown by parts of libc++ when an arithmetic conversion overflows (e.g. `std::stoi`, `std::stoull`, `std::strtonum` analogues, or explicit `__throw_overflow_error`). With no stack frames symbolicated, we can't pinpoint the call site. Given the high impact (14k users) and HybridApp-only platform, it most likely originates in C++ code shared with OldDot (Bedrock client, AppContext, or one of the native modules), but this needs a dSYM-resolved event before any guess.

Reproduction

Unknown. The crash fires without a JS culprit recorded in Sentry; needs investigation by re-symbolicating recent events or attaching a debugger to a build that reproduces.

Investigation suggestions

  • Pull a recent event in Sentry and re-symbolicate against the matching dSYM
  • Group by `device.family` and `os` in Sentry to see if it correlates with iOS 26 or a specific device class
  • Cross-check Mobile-Expensify crash buckets (Firebase Crashlytics / mobile.expensifyg) for the same overflow_error signature

Related

  • Sibling Sentry IDs (if any): none identified
  • Prior GH issues: none found for `St14overflow_error` or `overflow_error`
Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @huult
Issue OwnerCurrent Issue Owner: @huult

Activity

  1. self-assigned this
    on May 21, 2026
  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on May 21, 2026
  3. MelvinBot commented on May 21, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    This is almost certainly caused by known iOS 26 compatibility issues with React Native's C++ layer, not by Expensify-specific code. The crash is a std::overflow_error thrown from unguarded C++ code in the React Native / Hermes runtime.

    The most likely crash vectors, in order of probability:

    1. Hermes PAC (Pointer Authentication) failure on physical iOS 26 devices — iOS 26 hardened pointer authentication enforcement, which invalidates raw pointer arithmetic in Hermes VM internals. This causes 100% crash rate on physical iOS 26 devices (facebook/hermes#1966, expo/expo#44356). Hermes heap corruption can cascade into std::overflow_error when a corrupted unordered_map attempts resize via __next_prime (firebase/firebase-ios-sdk#10931).

    2. TurboModule NSException → C++ exception crash path — RCTTurboModule.mm converts NSExceptions into C++ std::exception (via JSError) and rethrows on a background queue where nothing catches it (facebook/react-native#54859, expo/expo#44606). The HybridApp bridge module (ReactNativeHybridApp.mm) is a TurboModule, making it directly susceptible.

    3. Unguarded std::stoi/std::stoul calls in RN dependencies — several call sites lack try/catch and will throw std::overflow_error on large numeric strings:

      • CSSAnimationConfig.cpp:129 — std::stoul in Reanimated CSS animations (no guard)
      • StackTraceParser.cpp:262-263 — std::stoi in Hermes stack trace parsing (no guard)
      • DynamicEventPayload.cpp:33 — std::stoi in Fabric event system (no guard)

      Expensify already patched one such site in Reanimated's LayoutAnimationsProxy_Legacy.cpp (patch), confirming this is a known crash vector.

    The crash being HybridApp-only and correlating with iOS 26.x strongly points to vectors #1 or #2, since HybridApp exercises more native bridge code and the iOS 26 C++ runtime changes affect Hermes most severely.

    What changes do you think we should make in order to solve the problem?

    This is an upstream React Native / Hermes issue that requires fixes from Meta. The recommended approach:

    1. Short-term: Upgrade React Native to a version that includes iOS 26 PAC fixes for Hermes (once Meta ships one — tracked in facebook/react-native#55337)
    2. Short-term: Patch unguarded std::stoi/std::stoul calls similar to the existing Reanimated patch — add catch(...) guards around CSSAnimationConfig.cpp:129, StackTraceParser.cpp:262, and DynamicEventPayload.cpp:33
    3. Diagnostic: Re-symbolicate a recent Sentry event against the matching dSYM to confirm the exact crash site. Check if the exception message is __next_prime overflow (heap corruption) vs stoi/stoul (direct overflow). Also filter by device type (physical vs simulator) and check if crashes correlate with app launch vs mid-use

    What alternative solutions did you explore? (Optional)

    • Investigated first-party C++ code in the App repo (.mm/.cpp files) — none perform numeric string conversions that could overflow
    • The Mobile-Expensify submodule (OldDot native code) could not be inspected as it's a private submodule, but given the strong correlation with known iOS 26 + Hermes issues affecting the entire React Native ecosystem, the upstream cause is far more likely
    • Considered whether this is device-class specific (e.g., older hardware) — unlikely given 14k+ affected users
    Investigation details

    Evidence chain:

    • iOS 26 introduced hardened PAC enforcement → Hermes VM uses raw pointer arithmetic → pointer operations fail → heap corruption → unordered_map resize hits __next_prime overflow → std::overflow_error
    • Alternatively: iOS 26 API changes cause NSException in native module → RCTTurboModule.mm converts to C++ exception → uncatchable on background queue → fatal crash
    • HybridApp-only because it exercises more native bridge surface area (TurboModule bridge, OldDot integration) than standalone NewDot builds

    Upstream tracking:

    Existing Expensify mitigation:

    Confidence: Medium — the iOS 26 + Hermes connection is well-documented across the React Native ecosystem, but without a symbolicated stack trace we cannot confirm the exact crash site.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR (for the std::stoi/std::stoul patches), @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on May 21, 2026
  5. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on May 21, 2026
  6. melvin-bot commented on May 21, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @nyomanjyotisa (External)

  7. melvin-bot commented on May 21, 2026

    @melvin-bot
  8. changed the title [-][Sentry: APP-8KQ] iOS Fatal C++ Exception: St14overflow_error[/-] [+][$250] [Sentry: APP-8KQ] iOS Fatal C++ Exception: St14overflow_error[/+] on May 21, 2026
  9. yuvrajangadsingh commented on May 21, 2026

    @yuvrajangadsingh
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    The HybridApp iOS build is fatally crashing with std::overflow_error and Sentry has no symbolicated stack trace to work with, so the crash call site in the OldDot C++ layer can't be identified or fixed.

    What is the root cause of that problem?

    There are two problems chained together. First, the dSYMs needed to symbolicate the crash are never uploaded to Sentry. The iOS CI in .github/workflows/buildIOS.yml does collect dSYMs from the xcarchive at lines 289-314, zips them, and uploads them as a GitHub Actions artifact (ios-dsym-artifact). The deploy workflow downloads that artifact at deploy.yml:337-344 and sets dsymPath in env at line 363. But no step ever calls sentry-cli debug-files upload. The only Sentry CLI usage in buildIOS.yml:248-262 uses sentry-cli build upload with --build-configuration Release, which is for Sentry's binary size tracking, not crash symbolication. The result: every native C++ crash from HybridApp lands in Sentry with "No stacktrace available" because Sentry can't resolve frame addresses without the matching dSYMs.

    Second, there is an actual std::overflow_error being thrown somewhere in the OldDot C++ layer. std::overflow_error has a short list of callers in libc++: std::stoi / std::stol / std::stoull and their cousins when the input string represents a value larger than the target type can hold, and std::bitset::to_ulong() when the bitset has more bits set than fit in unsigned long. The most likely candidate in the HybridApp bridge path is numeric account ID parsing. In src/libs/actions/Session/index.ts:388 and src/libs/actions/Delegate.ts:233, NewDot passes accountID: String(session.accountID) through HybridAppModule.switchAccount to OldDot. If OldDot's C++ code parses that string with std::stoi() (signed 32-bit), any account ID above 2,147,483,647 overflows. But confirming this requires a symbolicated event, which the first fix enables.

    What changes do you think we should make in order to solve the problem?

    Add a sentry-cli debug-files upload step to .github/workflows/buildIOS.yml right after the existing upload-dsym step (after line 315). The DSYM_PATH output from the zip-dsyms step and the SENTRY_AUTH_TOKEN secret are both already in scope at that point:

    - name: Upload dSYMs to Sentry for crash symbolication
      if: steps.zip-dsyms.outputs.DSYM_PATH != ''
      continue-on-error: true
      timeout-minutes: 10
      run: |
        npx sentry-cli debug-files upload \
          --org expensify --project app \
          "${{ steps.zip-dsyms.outputs.DSYM_PATH }}"
      env:
        SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

    After merging this, trigger a reprocess on APP-8KQ in Sentry (Sentry can reprocess existing events once the matching dSYMs are uploaded). The symbolicated frames will show the exact C++ function throwing the overflow. If it's account ID parsing as suspected, add a range check in Mobile-Expensify's OldDot bridge before passing the string to whichever std::stoi / stol call is involved. If it turns out to be something else entirely (bitset, font metric, layout integer), the symbolicated trace will point right at it.

    What alternative solutions did you explore? (Optional)

    I looked at wrapping the HybridAppModule.switchAccount and signInToOldDot calls in NewDot with a try/catch to convert the uncaught C++ exception into a JS-level error that Sentry would capture with a JS stack trace. That would stop the fatal crash but hides the overflow rather than fixing it, and C++ exceptions thrown across the Objective-C bridge boundary don't reliably surface as catchable JS errors anyway. Getting the dSYMs into Sentry and finding the exact site is the right order of operations.

    working branch with the fix applied: diff

  10. Abdukhamid000 commented on May 21, 2026

    @Abdukhamid000
    Contributor

    Proposal

    What is the root cause of that problem?

    I want to be upfront: with No stacktrace available in Sentry, any specific call site is a hypothesis until we re-symbolicate. That said, std::overflow_error in libc++ is almost exclusively raised by std::stoi / std::stol / std::stoll (and their u/f/d variants) when the parsed integer doesn't fit the target type — __throw_overflow_error is otherwise rarely invoked directly. So the call site is almost certainly an unguarded std::sto* parsing a string whose source can be larger than INT_MAX.

    Grepping the iOS-linked C++ in our tree, the unguarded std::stoi call sites that are reachable in a production HybridApp build live inside react-native itself:

    The StackTraceParser path is my top suspect because (a) it fires on every JS exception, (b) HybridApp loads a much larger merged bundle than standalone NewDot, which is consistent with the iOS-HybridApp-only platform breakdown, and (c) the throw happens deep in libc++ with no JS culprit recorded — which matches Sentry's "no stacktrace available, no JS breadcrumb" symptom exactly.

    There is also a non-trivial chance the actual offender lives in OldDot / Bedrock C++ inside Mobile-Expensify. I couldn't search that submodule from this checkout, so step 1 of the investigation must be symbolication.

    What changes do you think we should make in order to solve the problem?

    Step 1 — Re-symbolicate before committing to a fix. Pull a recent event from APP-8KQ, upload the matching dSYM, and confirm whether the top frame lands in StackTraceParser, DynamicEventPayload, or somewhere in OldDot. Until we see a frame, anything else is a guess.

    Step 2 — Land a defensive guard regardless. Even if the symbolicated frame points elsewhere, the StackTraceParser overflow is a real latent bug that will start crashing the JS error reporter the moment someone throws from a long minified line. The fix is a tiny helper that swallows out-of-range and falls back, applied at the three call sites:

    -      int line1Based = std::stoi(match[5].str());
    -      int columnOrOffset = std::stoi(match[6].str());
    +      int line1Based = parseIntOrZero(match[5].str());
    +      int columnOrOffset = parseIntOrZero(match[6].str());

    with the helper:

    + static int parseIntOrZero(const std::string& s) {
    +   try {
    +     return std::stoi(s);
    +   } catch (const std::out_of_range&) {
    +     return std::numeric_limits<int>::max();
    +   } catch (const std::invalid_argument&) {
    +     return 0;
    +   }
    + }

    The same wrapper replaces the std::stoi(match[1].str()) for the skipped-frames count and the std::stoi(key) in DynamicEventPayload.cpp. These three changes belong upstream in facebook/react-native; we should open a PR there and, in the meantime, carry it as a patch-package entry so we stop bleeding users on 9.3.77.

    Step 3 — Once symbolicated, fix the real call site. If step 1 points at OldDot/Bedrock C++, apply the same try/catch pattern there. If it points at a different RN module, same treatment. The pattern is identical: never let std::sto* escape across an FFI boundary unguarded.

    What alternative solutions did you explore?

    • Use std::from_chars instead of std::sto*. It's the "right" fix because it returns an error code instead of throwing, but it requires touching every call site individually and changing signatures, which is a bigger upstream change. The try/catch wrapper is a one-line drop-in that's easy to land via patch-package today; from_chars is a good follow-up for the RN PR.
    • Global C++ terminate handler that logs and resumes. Rejected — masking std::overflow_error globally hides genuine bugs and breaks the contract that a fatal exception is fatal. We want the specific known-safe sites guarded, not the whole process.
    • Wait for symbolication before doing anything. Rejected because (a) Sentry has had this issue open since 2026-03-27 without progress, (b) the StackTraceParser overflow is independently verifiable from the source, and (c) the guard is so cheap that landing it costs us nothing even if the real culprit turns out to be elsewhere.
  11. 37 remaining items

  12. melvin-bot commented on Jun 5, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.99-9 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-06-12. 🎊

    For reference, here are some details about the assignees on this issue:

    • @huult requires payment through NewDot Manual Requests
    • @war-in does not require payment (Contractor)
  13. changed the title [-][Due for payment 2026-06-04] [$250] [Sentry: APP-8KQ] iOS Fatal C++ Exception: St14overflow_error[/-] [+][Due for payment 2026-06-12] [Due for payment 2026-06-04] [$250] [Sentry: APP-8KQ] iOS Fatal C++ Exception: St14overflow_error[/+] on Jun 5, 2026
  14. melvin-bot commented on Jun 5, 2026

    @melvin-bot

    @huult @mallenexpensify The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]

  15. mallenexpensify commented on Jun 6, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @huult due $250 via NewDot

  16. JmillsExpensify commented on Jun 29, 2026

    @JmillsExpensify
    Contributor

    $250 approved for @huult

  17. mountiny commented on Jul 2, 2026

    @mountiny
    ContributorAuthor

    Fresh Sentry numbers for APP-8KQ (closed tracker — still seeing events):

    • Users (last 7d): 12
    • Events (last 7d): 12
    • Platform: iOS HybridApp — C++ overflow_error
    • Last seen: 2026-06-30

    Reopening optional — low volume but ongoing.

  18. moved this from Done to SUBISSUE in [#whatsnext] #qualityon Jul 2, 2026
  19. removed their assignment
    on Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions