#1634

Add an embedded JavaScript engine

Openfazxeswants to merge intofrom

Summary

  • Add fx's own copy of quickjs-ng v0.18.0 in src/js/quickjs/, without its file, process, and timer modules. This is the first third-party C code fx builds. The maintainers agreed to add it for code mode, which needs a JavaScript runtime that every fx binary carries and that gives a program no file, network, or process access. FORK.md records the release, its checksum, a script that compares the copy with upstream, and the one change fx makes to it.
  • Make the copy's array loops, such as join, reverse, sort, and slice, check the deadline on each pass. They walk an object up to its length, which a program can set as high as 2^53 - 1, and before this change Array.prototype.join.call({ length: 2 ** 27 }, '') ran for more than a second against a 200 ms limit.
  • Add src/js/engine.zig, which runs one program in a fresh runtime with limits on its heap (64 MiB), its time (30 s), and its native stack (4 MiB in Debug builds, 1 MiB otherwise). A program that hits the heap limit stops with an error it can't catch. The heap also has a cap of its own, because QuickJS's limit doesn't count the 4 KiB arenas it carves small blocks from.
  • Report a timeout, not a thrown error, when the deadline passes while a program's own toJSON or toString runs, and keep thrown text valid UTF-8 when a program throws a lone surrogate.
  • Run the engine's tests with zig build test-js, which zig build test also runs. Nothing in the fx binary uses the engine yet.
  • Run the macOS check for any change to the engine's C.
  • Describe src/js/ in AGENTS.md, and add quickjs-ng's license to THIRD_PARTY_NOTICES.md.

Architecture

Before, fx had no JavaScript engine:

flowchart LR
  test["zig build test"] --> fxtests["fx unit tests"]
  fx["fx binary"]

After, the engine is its own module, built and tested by zig build test-js. The fx binary doesn't link it yet:

flowchart LR
  test["zig build test"] --> fxtests["fx unit tests"]
  test --> testjs["zig build test-js"]
  testjs --> engine["src/js/engine.zig: run"]
  engine --> bindings["src/js/quickjs.zig"]
  engine --> heap["src/js/capped_heap.zig"]
  bindings --> fork["src/js/quickjs/: quickjs-ng v0.18.0 fork"]
  heap --> fork
  fx["fx binary"] -.-|not linked yet| engine