Skip to content

refactor(codemode): extensions are host functions only - #49196

Merged
rekram1-node merged 1 commit into
v2from
extensions-functions
Sep 15, 2026
Merged

rekram1-node merged 1 commit into
v2from
extensions-functions

Conversation

@rekram1-node

Copy link
Copy Markdown
Collaborator

An extension is a set of named host functions. Arguments come in as copies, results go out as copies, and a function inside a result is callable the same way. The class mirror (constructors, prototype walk, statics, accessors, Handle, brand checks) is deleted.

Why

A leak audit of extensions.ts found both rank-1 host-corruption paths in the reflection layer, not the copy seam: static lookup walked past exposed classes into EventEmitter/Object, and a sloppy constructor called without new ran with this === globalThis. Every embeddable runtime (QuickJS, V8 templates, workerd, JSI, Deno ops, Lua userdata, WIT resources) registers functions one at a time and never discovers members by walking a prototype chain. This PR moves to that shape.

What changes

 Extension.make({ name, globals })
   globals values:
-    class      → mirrored: constructor, prototype methods, statics, accessors, inheritance,
-                 per-run prototypes, Handle wrapping, WeakMap identity, brand checks
     function   → callable; args copied in, result copied out
+    function inside a result → callable, same rules, named by path in diagnostics (`fetch.json`)
 src/interpreter/extensions.ts   315 → 175 lines
-  expose / members / handlePrototype / protoOf / exposed / classOf / handles
-  accessor definition, static walk, "Illegal invocation"
+  wrap(value, label)       one host function → one Native; used for globals and nested functions
   toHost / fromHost        (kept; fromHost gains the function row)
-  hostErrors: Record       (`e.name = "constructor"` reached Object)
+  hostErrors: Map
 src/interpreter/objects.ts
-  class Handle
 src/interpreter/references.ts
-  Handle rows in isRuntimeReference / describeValue

What an extension looks like now

Extension.make({
  name: "web",
  globals: {
    fetch: async (url: string, init?: { method?: string }) => {
      const res = await globalThis.fetch(url, init)
      const bytes = new Uint8Array(await res.arrayBuffer())
      return {
        status: res.status,
        ok: res.ok,
        headers: { get: (name: string) => res.headers.get(name) },
        text: () => new TextDecoder().decode(bytes),
        json: () => JSON.parse(new TextDecoder().decode(bytes)),
        bytes: () => bytes,
      }
    },
  },
})
const res = await fetch("https://api.example.com/users")
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return await res.json()

The closure is the handle: host state stays in the host closure, the program never holds a host object, and there is no this, no brand check, and nothing to reflect over.

direction value result
in data, Date, RegExp, URL, URLSearchParams, Map, Set, Uint8Array, Error copy
in function, generator, promise, symbol TypeError
out data, wrappers, ArrayBuffer, Error copy
out function callable Native
out class instance, symbol, BigInt, other typed arrays TypeError

Lost

new X(), instanceof X, and accessors on host objects. If a program ever needs to construct host objects, the next step is a declared method table, not a prototype walk.

Checks

  • bun test 5119 pass / 0 fail (extensions.test.ts 28 → 22, class cases replaced by nested-function cases)
  • test262 3858 pass / 426 known-gap, unchanged
  • bun run check green
  • interpreter-support.md Extensions section rewritten

@rekram1-node
rekram1-node merged commit d9dfc5c into v2 Sep 15, 2026
7 of 8 checks passed
@rekram1-node
rekram1-node deleted the extensions-functions branch September 15, 2026 18:42
jinhuang712 pushed a commit to jinhuang712/opencode that referenced this pull request Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant