CYNIC
A strict-only, hardened-by-default ECMAScript engine

Cynic.

A JavaScript engine that has heard enough.

Strict-only, and hardened from the first line — the primordials freeze before your code runs, so a dependency can't redefine Array.prototype out from under you. It already parses, compiles, and runs real JavaScript: async, generators, a module graph, proper tail calls. And WebAssembly, through a second engine built from scratch — called Sarcasm, because of course. Everything the language spent twenty years trying to forget, it forgets on purpose: no with, no sloppy mode, eval off unless you ask. Ready for production? Eventually.

Pre-alpha. Parser, interpreter, and GC ship; a baseline JIT (Bistromath) hides behind --jit; spec coverage is still climbing. Read docs/ROADMAP.md before opening issues. Better yet, don't.
§01 · REFUSALS

Things Cynic won't do for you.

Every feature an engine ships is surface it can never take back. Cynic carries less of it by design — the legacy syntax, the sloppy-mode quirks, the deprecated globals, and the sharper tools a hardened runtime is better off without.

REFUSED
with (obj)

The with statement

Allegedly part of the language. We checked. We disagree.

SyntaxError: with? in 2026? please.
RETIRED
0o7 → 07

Legacy octal literals

The 0o prefix exists for a reason. The bare-leading-zero form does not.

SyntaxError: legacy octal: not in strict mode, not anywhere.
DENIED
function f() in if(){}

Function-in-block, sloppy-mode style

Annex B's sloppy-only function-in-block hoisting is gone. Declare it where it lives.

SyntaxError: Annex B not on the menu.
PASS
for (var i in o = {}) {}

Initializer in for-in head

Annex B accident. Doesn't parse here. Hoist your initializer like an adult.

SyntaxError: for-in head is not for assignment.
NEVER
<!-- -->

HTML comments in source

Yes, that's real ECMAScript. Yes, we are pretending it isn't.

SyntaxError: this is a JS file. compose yourself.
NOPE
escape() · unescape()

The pre-URI globals

Use encodeURI / encodeURIComponent. They've been correct since 1999.

ReferenceError: 'escape' is not shipped.
EXPIRED
"hi".bold()

The String.prototype HTML wrappers

All thirteen of them — anchor, bold, fontcolor, … — left at the door. This is a string, not an HTML formatter.

TypeError: 'bold' is not a string method here.
NOPE
d.getYear()

Date.prototype.{getYear, setYear}

The two-digit-year ones. Cynic keeps the normative aliases (substr, trimLeft, toGMTString) — these don't make the cut.

TypeError: 'getYear' is not on this Date.
OFF BY DEFAULT
eval() · new Function("…")

Runtime code construction

Off by default — code is code here, not strings assembled at runtime. Aligns with SES / Hardened JavaScript and removes the supply-chain bait. --allow=eval opens a real evaluator when you mean it; the frozen primordials still box it in. Multi-file scripts use a host hook, not user-reachable.

EvalError by default. --allow=eval if you insist.
STRICT MODE isn't a mode. it's a worldview.
§02 · WHERE WE ARE

Pre-alpha. Not pretending otherwise.

Lexer, parser, and bytecode interpreter ship. The runtime is filling in. Mark-sweep GC ships; a baseline JIT runs behind --jit, with the optimising tier and a generational GC still future. Below is what works, what kind-of works, and what Cynic refuses to pretend works yet.

The receipts
Cynic says
What that means
test262 · parser
scoped to the corpus Cynic targets
100% attempted.
Every fixture in scope either parses or correctly rejects. Spec coverage is the next number to grow.
test262 · runtime
parse → compile → execute
Getting there.
Bigger than last week. Smaller than next week.
In-tree unit tests
tests-first, allegedly
A lot.
Quadruple digits. Nobody's bragging.
Promises & async/await
full chaining; pending-await via JSGenerator
Works.
Yes, even the part where it suspends.
Async generators
yield-await chaining via promise reactions
Works.
Surprisingly. We checked.
yield* delegation · for await of
sync + async generators; IteratorClose on every abrupt path the spec describes
Works.
We learned §15.5.5 by failing it, then we stopped failing it.
Top-level await
thenable adoption + suspension across module boundaries
Works.
Async modules suspend. Sibling modules don't block. The spec said so.
RegExp · full ECMA-262
backrefs, named groups, lookaround, u/v flags
Works.
Perlex — Cynic's own matcher. The vendored regex engine got retired.
WebAssembly
the WebAssembly JS API — compile / instantiate, Module / Instance / Memory / Table / Global; SIMD, reference types, memory64, multi-memory, tail calls, exception handling, cross-module linking
Works.
Cynic does WebAssembly too. With Sarcasm — its own from-scratch engine, 100 % of the spec testsuite. Off by default; --allow=wasm.
Real Symbol & BigInt primitives
NaN-boxed, pointer-tagged; well-known symbols, registry, arbitrary-precision BigInt
Works.
Primitives. Not polyfills wearing a costume.
TypedArrays + ArrayBuffer + DataView
full %TypedArray%.prototype surface
Works.
All the views. All the bytes. None of the drama.
Proxy traps
get / set / has / deleteProperty / defineProperty / ownKeys / getPrototypeOf / setPrototypeOf / isExtensible / preventExtensions / apply / construct
Works.
Every trap the spec wrote down. Apologies to your debugger.
Real module graph
cyclic imports, indirect-binding TDZ, namespace [[Get]] ReferenceError on uninit, dynamic import()
Works.
Cyclic. TDZ on indirect imports. The Namespace [[Get]] ReferenceError the spec asked for.
Dynamic import()
host-loader-backed; non-Promise & settled-Promise cases through await
Works.
Returns a real Promise. The loader is up to the host. The body is up to the spec.
Import attributes · with { type: "json" }
JSON & text module records, attribute-keyed module cache, static + dynamic import()
Works.
JSON modules without the assertion-vs-attribute drama. Cache keys on the type.
ES2025 collection & Promise additions
Set union / intersection / difference / symmetricDifference / isSubsetOf / isSupersetOf / isDisjointFrom · Promise.try · Promise.withResolvers
Works.
Shipped. Each one cost the runtime score nothing and the spec a little dignity.
Map / WeakMap getOrInsert (upsert)
Stage 4 (2026-05); default-on, no flag
Works.
Graduated to Stage 4 this month — the flag came off, the method stayed.
Explicit Resource Management · using / await using
Symbol.dispose / Symbol.asyncDispose, DisposableStack / AsyncDisposableStack, SuppressedError; disposal on every abrupt path
Works.
Scopes clean up after themselves. The finally you keep forgetting to write.
Experimental · Iterator.zip · ShadowRealm
joint-iteration (Stage 3) + ShadowRealm (Stage 2.7), behind --enable=<name> or --enable-experimental; off by default. Each scored as its own isolated test262 sweep.
Opt-in.
Pre-Stage-4: shippable, but the committee can still change its mind. Cynic takes the trade; embedders decline by default.
WeakRef · FinalizationRegistry
§26.1 / §26.2; real weak references — major sweep clears dead targets and queues registry callbacks
Works.
Genuine weakness. The collector finally honours its end of the contract.
Garbage collector
generational mark-sweep — short-lived objects collected without scanning the whole heap
Works.
Most objects die young. The collector finally agrees.
Proper Tail Calls (PTC)
ECMA-262 §15.10; return f(x) reuses the caller's frame instead of pushing a fresh one
Works.
Second JS engine ever to ship this. JavaScriptCore was first in 2016. V8 tried and walked it back. SpiderMonkey's tracking bug is open since 2015.
Inline property caches
monomorphic shape ICs on lda_property / sta_property / call_method + proto-load; /perf & /profile harness scaffolded
Works.
Reads land on the slot in one shape compare. No speculation involved.
Intl · the internationalization API
a separate opt-in build — CLDR/ICU + IANA tzdata, named time zones, non-ISO calendars; the default stays ISO / C locale
Future Cynic's problem.
Not refused — just heavy. A locale database is a lot of bytes to ship by default.
SharedArrayBuffer · Atomics
shared-memory primitives — Atomics.wait / notify / waitAsync, cross-agent notify, a multi-agent realm pool; the SES isolation boundary stays intact
Works.
Was out by policy, then a design problem. Now it just works — wait, notify, waitAsync, across agents.
JIT tiers
Bistromath — a baseline JIT — on by default; --no-jit opts out. The optimising tier (Ohaimark) is still future.
In progress.
There's a JIT now, and it's done hiding — on by default. The optimising tier is future Cynic's problem.

Full per-bucket scoreboard, history, and per-day deltas live in test262-results.md.