lamdera / lamdera/compiler

Wire3 decoder pipeline causes 'too much recursion' on Firefox

Open
#83 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Haskell
Stars
95
Forks
21
PR merge metrics
No merged PRs in 30d

Description

Bug

Lamdera apps with large FrontendModel records crash on Firefox with:

Uncaught internal error: too much recursion

The app works fine on Chrome and works locally (lamdera live) but fails in production (lamdera deploy).

Root Cause

In extra/Lamdera/Wire3/Decoder.hs (line 459), decodeRecord generates a decoder pipeline using foldlPairs (|>):

succeedDecode (\a b c ... -> { field1 = a, field2 = b, ... })
    |> andMapDecode decoder1
    |> andMapDecode decoder2
    ...
    |> andMapDecode decoderN

Each |> compiles to a nested function call in JS. For apps with deeply nested records (e.g. a FrontendModel containing OwnerData containing forms, lists, etc.), the total chain can reach 380+ levels of nesting.

In the compiled production JS, this becomes:

i(X, i(Y, i(Z, ... i(W, u(fn)) ...)))
//  380+ nested calls

Firefox's call stack limit is ~5000 frames. With ~12-15 frames per decoder step, 380 steps × ~13 frames ≈ ~5000 frames → stack overflow on Firefox.

Chrome has a higher stack limit (~8800+) so it works there.

How to Reproduce

  1. Create a Lamdera app with a FrontendModel that has many fields (including nested records with many fields)
  2. Deploy to production (lamdera deploy)
  3. Open the deployed app in Firefox → "too much recursion" error
  4. Same app works in Chrome and locally with lamdera live (non-optimized build)

Evidence

Analysis of the compiled production JS (frontend.*.js):

  • Global max parenthesis nesting depth: 218 at the decoder chain
  • 380 consecutive i() calls (the compiled andMapDecode pipeline)
  • The i function is function(r,t){ return A4(function(n){ return L$(n,r) }, t) } — each call adds to the stack

Suggested Fix

In Decoder.hs, chunk the foldlPairs into groups to limit nesting depth:

Instead of:

foldlPairs (|>)  -- creates a single chain of N steps

Split into batches of ~50 fields, decode each batch separately, then compose the results. This would cap the nesting depth regardless of record size.

Alternatively, the encoder (Encoder.hs line 237-254) already avoids this issue by using a flat list:

encodeSequenceWithoutLength $ list fieldEncoders

A similar flat approach for the decoder would eliminate the problem entirely.

Environment

  • Firefox (any version — all have stricter stack limits than Chrome)
  • Lamdera production builds (optimized + minified JS)
  • App with ~100+ total fields across nested FrontendModel records

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in extra/Lamdera/Wire3/Decoder.hs at decodeRecord around line 459 and inspect how foldlPairs (|>) builds the decoder pipeline. Compare the approach with Encoder.hs lines 237-254 and reproduce the issue using the Firefox, production-build scenario described. Done means large nested FrontendModel records no longer trigger Firefox's recursion error while existing decoder behavior remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
elm, haskell
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.