Repository navigation
Conversation
- Add missing Lua 5.5.1 symbols to lauxlib.js - Update inventaire-api.md to reflect new symbols - Add comprehensive tests for all new symbols - Update package.json for @datamoc/fengari - Add README.md for the scoped package
…ial math order test
- lstrlib: add formatint() so %d/%i/%o/%u/%x/%X and string.pack/string.unpack
handle 64-bit Lua integers correctly (signed for d/i, unsigned for o/u/x/X);
rewrite packint/unpackint on BigInt with full-width sign handling and
unread-byte validation; align string.packsize('j'/'J') and the dumper
integer header on integer.SIZE; make Kint/Kuint overflow checks BigInt-safe.
- lmathlib: make integer overflow checks BigInt-safe.
- ltablib: guard table.unpack against overflow using LUAI_MAXSTACK instead of
Number.MAX_SAFE_INTEGER.
- test/math: restore the official lua-5.5.1-tests 'if floatbits < intbits then
... else ... end' conditional in 'testing order'; the port had dropped it and
hardcoded only the 'floats can represent all integers' branch, which cannot
hold for 53-bit doubles vs 64-bit integers.
- test/loadfile: regenerate the stale loadfile-test.bc artifact, which no
longer loaded with the current bytecode header.
- chore: drop the broken 'repository' block from package.json, remove unused
imports flagged by eslint, refresh the docs/lua-5.5.1 baseline.
- Restrict the 'files' whitelist to src/; the previous whitelist shipped the whole test/ tree and the Lua 5.5.1 reference archives (170 files, 1.0 MB). The published tarball is now 66 files / 147 kB. - Remove 'module' and 'types': the build script is still a placeholder, so dist/fengari.esm.js and dist/fengari.d.ts do not exist and would break bundlers and TypeScript resolution. - Point the README's compliance-docs link at the GitHub repo, since docs/ is no longer part of the tarball.
The README and npm keywords advertised "full Lua 5.5.1 compliance" while defs.js reports Lua 5.4.6 (LUA_VERSION_NUM 504) and none of the 5.5.1 alignment lots in docs/lua-5.5.1 is complete. The claims now describe 5.4.6 and point to the alignment work as in progress. CHANGES.md gains the 0.1.6 section (extended lauxlib symbols, packaging fix, 64-bit integer fixes already on master). Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
lua_setallocf(L, allocf, ud) installs a hook called synchronously as allocf(ud, nsize) before each internal Uint8Array allocation, charged at the full new size. Throwing refuses the allocation: inside a protected call the operation aborts with LUA_ERRMEM and the standard "not enough memory" object, exactly like a failing C allocator, and the state stays usable; a script-level pcall cannot swallow it. Outside any protected call the embedder's own exception propagates unchanged. The single choke point lives in src/lmem.js, a leaf module (the JS analogue of lmem.c) so both the root libraries and the runtime factories can reach it without breaking the composition order. Sites routed: the luaL_Buffer growth (string.rep/gsub/format), ZIO buffer growth, the VM concat, undump reads, and the small per-call temporaries that carry a state. Not charged, by design and documented: the stateless defs.js conversions an embedder drives itself, zero-size allocations, and the few sites with no lua_State at hand. lua_getallocf(L) returns [allocf, ud]; the console.warn stubs are gone. Verified by test/lalloc.test.js (round-trip, observation, refusal, pcall transparency, per-state isolation) and the full suite: 45 suites, 658 passing. Version 0.2.0. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
publish-npm.yml mirrors @datamoc/mw_games' proven path: triggered on a published release (or a manual re-run), gated by the same lint and test suite CI runs on every push, publishing with `npm stage publish` over the OIDC exchange - no NPM_TOKEN secret, and the staged tarball still needs the interactive 2FA approval a CI run cannot answer. The npmjs.org trusted publisher for @datamoc/fengari must list this repo and workflow filename, configured the way the mw_games one already is. ci.yaml: installs from the frozen yarn.lock instead of a floating `yarn`, runs the extended lauxlib suite as its own gate, and adds Node 26.x to the matrix the publish job runs on. CHANGES.md's 0.2.0 section drops its "En développement" marker now that the tag is cut. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
For a VM whose whole job is parsing untrusted input, the JavaScript security queries are the one automated check worth adding beyond lint and the suite. The scheduled variant is left out on purpose: scheduled workflows are disabled on forks by default, so push and pull_request are the live gates. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
setup-node's registry-url writes a ${NODE_AUTH_TOKEN} placeholder into
.npmrc; npm leaves the unset variable alone, but yarn 1 refuses to start
at all ("Failed to replace env in config"), which is what killed the
v0.2.0 publish run eleven seconds in. An empty NODE_AUTH_TOKEN on the
install step satisfies yarn's eager env replacement and changes nothing
for trusted publishing, which never uses NODE_AUTH_TOKEN.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
The per-step empty NODE_AUTH_TOKEN was not enough: every yarn invocation re-reads npm config at startup and dies under the unexpanded placeholder, so `yarn lint` failed the same way `yarn install` had. registry-url is what makes setup-node write that placeholder, and it buys nothing here - npm's default registry is already registry.npmjs.org - so the job now writes no .npmrc at all and OIDC proceeds exactly as before. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
…, lua_closethread The 0.2.0 notes claimed this support but no such program compiled. const/close attributes (suffix and 5.5 prefix form), read-only checks, to-be-closed variables closed on block exit, break, goto, return and error, generic-for closing value, coroutine.close, lua_closethread/lua_toclose and real lua_resetthread/lua_closeslot. A __close cannot yield. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…version FengariVm gains Coroutine (resume/close), Yield from host functions, libs/remove options, LuaRef/Userdata/LuaFloat, BigInt for 64-bit integers, Uint8Array for binary strings, Map for exotic table keys, runMulti/callMulti, and is now exported from the fengari entry point. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- FengariVm option `sandbox`: safe libraries only, no dofile/loadfile/ string.dump, text-only load. - FengariVm option `limits` / setLimits: instruction and wall-clock budgets per top-level call through a count hook; LimitError; not escapable with pcall or coroutines. - Hardening: __proto__ keys, cross-VM references, package.loaded removal. - docs/benchmark: browser page comparing Lua in Fengari with plain JavaScript, plus tools/build-benchmark.js (npm run bench:build). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s, benchmark page Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…g, large allocations), isolate sandbox from host files Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r, demo page - lua2js / FengariVm.toJs: readable JavaScript from Lua source, with TODO(lua2js) markers and warnings for what cannot be translated - Literal Lua patterns become RegExp calls - docs/lua2js: paste Lua, get JS, benchmark against the interpreter - Optional numeric compilation (vm.compile) and its benchmark - Lua 5.5.1 migration docs and related VM updates Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A Lua line comment containing a bare CR, U+2028 or U+2029 ended the generated // comment early, so the rest of the comment ran as code. Replace those characters with spaces and add a regression test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- src/fengari.d.ts + "types" in package.json; test/types.test.js keeps the declarations and the runtime exports in sync - vm.throwLuaError(L, index = -1); Coroutine#close uses it - CHANGES: lua2js comment fix (previous commit) and these additions Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Lua errors raised through the C API inside the callback (failing __index, luaL_error, limit overrun) become LuaError / LimitError and the stack is restored; a JavaScript error from the callback is rethrown unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Hosts use these FengariVm members next to read/push. 0.5.2. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ons.node luaconf.js required 'os' whenever `process` existed, and a bundler's process shim also made ldblib.js load the empty readline-sync stub and crash. One src/isnode.js replaces `typeof process` in 13 places, os.platform() becomes process.platform, and package.json gets a "browser" field mapping the Node-only modules to false. 0.5.3. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e budgeted, `or` keeps Lua truth - every host entry point fails with a LimitError when a limit was hit during the call, even if Lua caught the failure of a coroutine/pcall - Coroutine#close() runs its __close handlers with a fresh budget - call/callMulti/compile/coroutine resolve their global path in a protected call under the limits (an __index on _G could loop forever) - lua2js: `a or b` is no longer `a ?? b` (differs for false); the old form is available as the nullishOr option / --nullish-or - 0.5.4 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… union - sandbox: true no longer defines the __random helper (nothing in Fengari calls it); like __fs_read/__fs_list it stays outside the sandbox - print: 'buffer' | 'native' | false, so a host that wants Lua's print does not re-open libraries (which restored string.dump) - LuaRef.type is 'function' | 'thread' | 'userdata' (LuaRefType) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Member
|
I don't know about you @daurnimator , but I'm not going to even consider this vibe-coded PR. |
Member
Agreed. This does way too much for one PR in any case. @datamoc start piecemeal and a PR should only solve one small problem at a time. We don't have an AI policy yet but you should be prepared to answer questions on why things were done a particular way without referring back to AI. i.e. you need to understand what your AI is suggesting at least as well as what you expect us to. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Why
The starting point was running a game campaign using Lua in the browser on top of Fengari. The first thing that failed was the bundle itself: Fengari called
require('os').platform()(src/luaconf.js), which a browser bundler cannot resolve, and consumers had to write a local shim.Behind it was a list of things the game's Lua needs from its VM:
Upstream Fengari (v0.1.5) is Lua 5.3.4 with 32-bit integers, no
<const>/<close>, no host-driven coroutines, and aluaconf.jsthat cannot be bundled without shims.This PR closes those gaps. Securing the VM for untrusted content, and the migration tooling, came out of that work.
What each goal needed
Bundling for the browser (0.5.3)
require('os').platform()is gone (process.platform).typeof processwas tested in 13 places, so a bundler'sprocessshim also madeldblib.jscrash at load on an emptyreadline-syncstub. One module,src/isnode.js(process.versions.node), replaces them.package.jsonhas abrowserfield mappingfs,os,path,tmp,child_processandreadline-syncto empty modules. esbuild, webpack, Vite and Browserify bundle thepackage without shims or
resolve.fallback. Verified withesbuild --platform=browser, with and without aprocessshim.Synchronous calls into game APIs (0.4.0, 0.5.x)
defineFunction(name, fn)registers a host function under a dotted path; Lua calls it synchronously and gets its return values (Multifor several).run,runMulti,call,callMultiandcallRefcall Lua and return results with value conversion in both directions (BigIntfor 64-bit integers,Uint8Arraystrings,
Mapfor exotic keys, 1..n tables as arrays,LuaFloat/preserveFloats).vm.protect(fn)runs a host callback in a Lua protected call, so Lua errors raised through the C API becomeLuaError/LimitErrorand the stack is restored.vm.throwLuaErrorbuilds the same error from a stack value.Callbacks retaining their identity (0.4.0)
LuaRef, anchored in the Lua registry and released withrelease()or by garbage collection. The same Lua function isvalue when called back with
callRef/LuaRef#call. ALuaReffrom another VM is refused.Userdatacarries opaque host values through Lua.read, not kept as references. Identity-preserving table references are not provided; a host that needs them must hold the table behind a Lua function or userdata.Dialogue coroutines (0.4.0)
vm.coroutine(path | LuaRef)andvm.coroutineFromSource(source)return aCoroutinewithstatus,resume(...args)andclose(). A host function returningnew Yield([...])suspends the calling coroutine; theresumearguments become the host function's return values. That is the shape of "pause for player input, resume when the player answers".Coroutine#close()runs pending<close>handlers and reports a failure.Real Lua modules and Lua 5.4 (0.1.6 – 0.3.0)
LUA_VERSION_NUM504). A "full Lua 5.5.1 compliance" claim had been added to the README (953794a) while the VM reports 5.4.6; it was removed (a0853cb).<const>and<close>(0.2.0 advertised them but no such program compiled): attribute grammar (the 5.5 prefix form too), compile-time const checks, to-be-closed variables closed on block exit,break,goto,returnand error, a newOP_TBCopcode, the closing value of genericfor,coroutine.close,lua_closethread,lua_toclose, reallua_resetthread/lua_closeslot. Known limitation: a__closecannot yield.files(read/list) lets the host serve module files, andlibs/removechoose the libraries a module can see.lua_setallocf) so memory can be bounded.Security hardening
Campaign content is third-party Lua, so the VM had to be safe to run it.
sandbox: true(base, coroutine, table, string, utf8, math only; noos,io,debug,package,require;dofile,loadfile,string.dumpremoved; text-onlyload; no host helper left as a global:__fs_read,__fs_list,__random).limits(instructions, wall-clock time, per-allocation and total allocation) per top-level call, with aLimitErrorthe script cannot catch.printoption ('buffer','native',false) so a host that wants Lua's ownprintno longer re-opens libraries, which restoredstring.dumpafter the sandbox removed it.__proto__keys cannot modify prototypes, conversion errors in host functions become Lua errors.Bypasses of the limits found and fixed along the way (do not use 0.4.x to 0.5.3 for untrusted scripts with
limits):xpcallran during the counting hook and could loop forever;find,match,gmatch,gsub),table.moveand plain-textfindwere not interruptible;limits.allocationandlimits.totalAllocation;sandboxexposed the host files through__fs_read/__fs_list;LimitErrorwhenever a limit was hit during the call;Coroutine#close()ran its handlers without a budget;call,compile,coroutine) ran before the budget, so an__indexon_Gcould loop forever or raise outside a protected call.Limitations: other native functions whose cost depends only on argument size (default-comparator
table.sort,table.concat,string.format) are not interrupted; boundlimits.allocation/totalAllocation.limitsonly acts on the VM created here, so combine it withsandbox.compileis deliberately disabled whilelimitsare active (documented in the README).Tooling added on the way
src/fengari.d.ts,"types"inpackage.json) so a TypeScript host needs no@ts-expect-erroror hand-written types;test/types.test.jschecks them against the runtime exports.lua2js/FengariVm.toJs: translates Lua to readable JavaScript for code that eventually leaves Lua, withTODO(lua2js)markers for what it cannot translate faithfully, and an online demo (docs/lua2js).a or bkeeps Lua's meaning (nullishOropts in to??). A Lua comment can no longer break out of the generated//comment (0.5.0 only).vm.compile) and a benchmark page comparing the interpreter, compiled Lua and JavaScript in the browser.Testing
test/browser-compat.test.js(fails if the library requires a Node-only module on the browser path),test/lattribs.test.js(const/close),test/lalloc.test.js,test/fengari-vm.test.js,test/fengari-vm-limits.test.js(every bypass above),test/types.test.js,test/ltranspile.test.js,test/numeric-compiler.test.js. 837 passing, 21 skipped; lint clean. The browser bundle was also built with esbuild and run with and without aprocessshim. All 34 files of the official Lua 5.5 test suite translate withlua2jsto syntactically valid JavaScript.