From 519400cd0d6a3be75bfb47d35be83b2e69fbe349 Mon Sep 17 00:00:00 2001 From: Fabio Rocha Date: Tue, 18 Aug 2026 12:17:46 +0100 Subject: [PATCH 1/4] Add builtin-city crash sweep test with deterministic repro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sweeps every city preloaded into the WASM engine, ticking each forward and failing on an engine trap ("RuntimeError: memory access out of bounds" out of simTick()). Not all cities lead to crashes for all seeds. On current code: - kobe traps at every seed tried (1, 42, 1234, 99999), from 30 turns up. - haight traps at seed 1 past ~240 turns; not previously suspected. Defaults (240 turns x seeds {1,42}) cover both known repros in ~7s for 64 runs. SIM_CRASH_QUICK=1 narrows to kobe/seed 42/30 turns; SIM_CRASH_TURNS, SIM_CRASH_SEEDS and SIM_CRASH_LIVE_CLOCK tune the sweep. The test currently fails — it is the reproduction for the fix to follow. Co-Authored-By: Claude Opus 5 (1M context) --- apps/micropolis/cli/simCrash.test.ts | 177 +++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 apps/micropolis/cli/simCrash.test.ts diff --git a/apps/micropolis/cli/simCrash.test.ts b/apps/micropolis/cli/simCrash.test.ts new file mode 100644 index 00000000..f8b474d2 --- /dev/null +++ b/apps/micropolis/cli/simCrash.test.ts @@ -0,0 +1,177 @@ +/** + * Crash sweep across every builtin city. + * + * Loads each city packaged into the WASM engine's virtual filesystem, ticks it + * forward a few in-game turns, and fails if the engine traps — the symptom + * being `RuntimeError: memory access out of bounds` out of `simTick()`. + * + * WHY THE CLOCK IS FROZEN + * + * These crashes look intermittent, but only because the engine seeds its RNG + * from the wall clock before anything JS-side can seed it: + * + * Micropolis::init() -> simInit() -> initWillStuff() -> randomlySeedRandom() + * Micropolis::loadCity() -> loadFile() -> initWillStuff() -> randomlySeedRandom() + * -> doSimInit() -> mapScan(0, WORLD_W) + * -> doSimInit() -> mapScan(0, WORLD_W) (again) + * + * randomlySeedRandom() (random.cpp) seeds `nextRandom` from gettimeofday(), and + * the doSimInit() mapScans that follow run the zone simulation over the freshly + * loaded city — doResidential/doCommercial/doIndustrial draw getRandom() and + * *mutate map tiles*. Every process therefore starts from a slightly different + * world, so a latent out-of-bounds access is hit on only some runs (kobe: ~60% + * of runs at 480 ticks). + * + * The engine's only route to wall-clock time is `emscripten_date_now`, which + * the generated glue defines as `() => Date.now()`. Freezing Date.now for the + * setup phase pins that seed, which makes the crash reproduce on *every* run — + * turning a flaky test into a deterministic one. That is the whole point: a + * crash test that fires 60% of the time is worse than useless in CI. + * + * Sweeping several seeds widens coverage without giving up reproducibility: + * each (city, seed) pair is a fixed, replayable starting world. + * + * Usage: + * pnpm --filter micropolis test cli/simCrash.test.ts + * SIM_CRASH_QUICK=1 pnpm --filter micropolis test cli/simCrash.test.ts # kobe only + * + * Env knobs: + * SIM_CRASH_QUICK=1 only kobe, seed 42, 30 turns — the tightest reliable repro + * SIM_CRASH_TURNS=n in-game turns per run (default 240) + * SIM_CRASH_SEEDS=a,b seeds to sweep (default 1,42) + * SIM_CRASH_LIVE_CLOCK=1 don't freeze the clock — reproduces the flakiness + * + * DEFAULTS ARE CHOSEN TO ACTUALLY CATCH THE BUG + * + * Measured against the current engine, under a frozen clock: + * - kobe traps at every seed tried (1, 42, 1234, 99999), from 30 turns up. + * - haight traps only at seed 1, and only once past ~240 turns. + * - deadwood — the originally reported crasher — survives all four seeds even + * at 240 turns, so its crash needs a starting world none of these seeds + * reach. It stays listed in KNOWN_CRASHERS as a reminder, not a skip. + * So this is not one city's bug: it is a latent out-of-bounds that longer runs + * surface in more cities. 240 turns x seeds {1, 42} covers both known repros in + * about 12s; trim with SIM_CRASH_TURNS / SIM_CRASH_QUICK when iterating. + */ + +import { describe, expect, it } from 'vitest'; +import { loadMicropolisMainModule } from '../src/lib/wasm/node'; +import { createNoopJsCallback } from '../src/lib/wasm/callbacks'; +import type { MainModule } from '../src/types/micropolisengine.d.js'; + +// Mirrors MicropolisSimulator.ts's cityFileNames — the cities preloaded into +// the WASM module's virtual filesystem, the only ones loadCity() can reach. +const BUILTIN_CITIES = [ + 'about', + 'badnews', + 'bluebird', + 'bruce', + 'deadwood', + 'finnigan', + 'freds', + 'haight', + 'happisle', + 'joffburg', + 'kamakura', + 'kobe', + 'kowloon', + 'kyoto', + 'linecity', + 'med_isle', + 'ndulls', + 'neatmap', + 'radial', + 'scenario_bern', + 'scenario_boston', + 'scenario_detroit', + 'scenario_dullsville', + 'scenario_hamburg', + 'scenario_rio_de_janeiro', + 'scenario_san_francisco', + 'scenario_tokyo', + 'senri', + 'southpac', + 'splats', + 'wetcity', + 'yokohama' +] as const; + +// Cities with a known or historically reported out-of-bounds trap. Under a +// frozen clock these are reproducible rather than flaky, so they stay in the +// sweep as regression coverage rather than being skipped. haight is in the list +// too: it traps at seed 1 past ~240 turns, which is how we learned this is not +// a kobe-specific bug. +const KNOWN_CRASHERS = ['deadwood', 'haight', 'kobe'] as const; + +// One turn is one cityTime increment = 16 ticks (simulate.cpp's 16-phase +// phaseCycle). +const TICKS_PER_TURN = 16; + +// Any fixed value works; the engine only needs the seed to stop moving. +const FROZEN_NOW = 1_700_000_000_000; + +const QUICK = process.env.SIM_CRASH_QUICK === '1'; +const FREEZE_CLOCK = process.env.SIM_CRASH_LIVE_CLOCK !== '1'; +const TURNS = Number(process.env.SIM_CRASH_TURNS ?? 240); +const SEEDS = (process.env.SIM_CRASH_SEEDS ?? '1,42').split(',').map((s) => Number(s.trim())); + +// Quick mode is the tightest reliable repro: kobe traps within 30 turns at any +// seed, so it needs neither the long horizon nor the seed sweep. Pinned +// explicitly rather than taken from SEEDS[0] so reordering SIM_CRASH_SEEDS +// cannot quietly change what "quick" means. +const QUICK_CITY = 'kobe'; +const QUICK_SEED = 42; +const QUICK_TURNS = 30; + +const cities = QUICK ? [QUICK_CITY] : BUILTIN_CITIES; +const seeds = QUICK ? [QUICK_SEED] : SEEDS; +const turns = QUICK ? QUICK_TURNS : TURNS; + +/** + * Run one city forward and return normally, or throw whatever the engine threw. + * + * A fresh WASM module per run is deliberate: it guarantees a zero-filled heap, + * so a trap is attributable to this city's trajectory and not to residue from a + * previous run. It also sidesteps the uninitialized `Micropolis::callback` + * pointer — setCallback() does `if (callback != NULL) delete callback` on a + * field the constructor never sets, which traps ("table index is out of + * bounds") for a second Micropolis allocated over freed memory. + */ +async function runCity(city: string, seed: number, runTurns: number): Promise { + const realDateNow = Date.now; + if (FREEZE_CLOCK) Date.now = () => FROZEN_NOW; + + let engine: MainModule; + try { + engine = await loadMicropolisMainModule(); + const micropolis = new engine.Micropolis(); + micropolis.setCallback(createNoopJsCallback(engine), {}); + micropolis.init(); + + const loaded = micropolis.loadCity(`/cities/${city}.cty`); + expect(loaded, `loadCity failed for "${city}"`).toBe(true); + + // Only meaningful once the clock-seeded setup above is done; before that + // initWillStuff() would just overwrite it. + micropolis.seedRandom(seed); + + for (let tick = 0; tick < turns * TICKS_PER_TURN; tick++) { + micropolis.simTick(); + } + } finally { + Date.now = realDateNow; + } +} + +describe(`builtin city crash sweep (${cities.length} cities x ${seeds.length} seed(s), ${turns} turns, clock ${FREEZE_CLOCK ? 'frozen' : 'live'})`, () => { + for (const city of cities) { + for (const seed of seeds) { + const known = (KNOWN_CRASHERS as readonly string[]).includes(city); + const label = `${city} survives ${turns} turns (seed ${seed})${known ? ' [known crasher]' : ''}`; + + it(label, async () => { + await expect(runCity(city, seed, turns)).resolves.toBeUndefined(); + }); + } + } +}); From 21da2d2b3fa23539617cdd96b9b5e7aabcdc2099 Mon Sep 17 00:00:00 2001 From: Fabio Rocha Date: Tue, 18 Aug 2026 12:51:57 +0100 Subject: [PATCH 2/4] Fix engine build under current Emscripten; regenerate WASM artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuilding the C++ engine with a current Emscripten (6.0.7) produced a module that aborted immediately on load in Node: Aborted(`Module.wasmBinary` was supplied but `wasmBinary` not included in INCOMING_MODULE_JS_API) Emscripten only honours Module.* properties named in INCOMING_MODULE_JS_API and hard-aborts on any it does not recognize. The default list omits both `wasmBinary` and `getPreloadedPackage`, which apps/micropolis/src/lib/wasm/ node.ts passes in order to load the engine from a Buffer instead of over HTTP. Spelling out the list (upstream default plus those two) fixes it. We do not know which Emscripten version produced the previously committed artifacts — the version string is not retained in the generated output, the wasm producers section is stripped, and the codegen fingerprints do not match any release we tried. Rather than keep guessing, this pins the build to what the toolchain does today and fixes the incompatibility, so `make install` works for anyone with a current emsdk. The regenerated micropolisengine.{js,wasm} are committed together with the makefile change, since the two only make sense as a pair. The .d.ts and .data outputs came out byte-identical and are unchanged. Verified no behavior change: the app test suite gives exactly the same results before and after (same 3 known simCrash failures, same 4 pre-existing monorepo.integration failures that need a prior `pnpm build`). Co-Authored-By: Claude Opus 5 (1M context) --- apps/micropolis/src/lib/micropolisengine.js | 802 ++++++++++-------- apps/micropolis/src/lib/micropolisengine.wasm | Bin 436059 -> 439159 bytes packages/micropolis-engine/makefile | 10 + 3 files changed, 471 insertions(+), 341 deletions(-) diff --git a/apps/micropolis/src/lib/micropolisengine.js b/apps/micropolis/src/lib/micropolisengine.js index e6aa63cc..c2cc08c8 100644 --- a/apps/micropolis/src/lib/micropolisengine.js +++ b/apps/micropolis/src/lib/micropolisengine.js @@ -5,8 +5,7 @@ // When targeting node and ES6 we use `await import ..` in the generated code // so the outer function needs to be marked as async. async function Module(moduleArg = {}) { - var moduleRtn; - + var Module = moduleArg; // include: shell.js // include: minimum_runtime_check.js (function() { @@ -25,9 +24,14 @@ async function Module(moduleArg = {}) { // Note: We use a typeof check here instead of optional chaining using // globalThis because older browsers might not have globalThis defined. - var currentNodeVersion = typeof process !== 'undefined' && process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; - if (currentNodeVersion < 180300) { - throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(180300) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + + // We skip the node version checking when running on Bun/Deno since the node + // version they report doesn't seem to be useful. + if (typeof process !== 'undefined' && !process.versions?.bun && typeof Deno == "undefined") { + var currentNodeVersion = process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; + if (currentNodeVersion < 180300) { + throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(180300) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + } } var userAgent = typeof navigator !== 'undefined' && navigator.userAgent; @@ -65,7 +69,6 @@ async function Module(moduleArg = {}) { // after the generated code, you will need to define var Module = {}; // before the code. Then that object will be used in the code, and you // can continue to use Module afterwards as well. -var Module = moduleArg; // Determine the runtime environment we are in. You can customize this by // setting the ENVIRONMENT setting at compile time (see settings.js). @@ -89,7 +92,7 @@ if (ENVIRONMENT_IS_NODE) { // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) -// include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmpkkkgbbly.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpix8say0m.js if (!Module['expectedDataFileDownloads']) Module['expectedDataFileDownloads'] = 0; Module['expectedDataFileDownloads']++; @@ -179,11 +182,6 @@ if (ENVIRONMENT_IS_NODE) { } Module['FS_createPath']("/", "cities", true, true); - for (var file of metadata['files']) { - var name = file['filename'] - Module['addRunDependency'](`fp ${name}`); - } - async function processPackageData(arrayBuffer) { assert(arrayBuffer, 'Loading data file failed.'); assert(arrayBuffer.constructor.name === ArrayBuffer.name, 'bad input to processPackageData ' + arrayBuffer.constructor.name); @@ -195,7 +193,6 @@ Module['FS_createPath']("/", "cities", true, true); var data = byteArray.subarray(file['start'], file['end']); // canOwn this data in the filesystem, it is a slice into the heap that will never change Module['FS_createDataFile'](name, null, data, true, true, true); - Module['removeRunDependency'](`fp ${name}`); } Module['removeRunDependency']('datafile_build/micropolisengine.data'); } @@ -207,10 +204,11 @@ Module['FS_createPath']("/", "cities", true, true); if (!fetched) { fetched = await fetchPromise; } - processPackageData(fetched); + await processPackageData(fetched); } - if (Module['calledRun']) { + // Detect whether the module JS file has already been loaded. + if (Module['FS_createPath']) { runWithFS(Module); } else { if (!Module['preRun']) Module['preRun'] = []; @@ -222,24 +220,24 @@ Module['FS_createPath']("/", "cities", true, true); })(); -// end include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmpkkkgbbly.js -// include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmp9cazitgs.js +// end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpix8say0m.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpe45adt7b.js // All the pre-js content up to here must remain later on, we need to run // it. if ((typeof ENVIRONMENT_IS_WASM_WORKER != 'undefined' && ENVIRONMENT_IS_WASM_WORKER) || (typeof ENVIRONMENT_IS_PTHREAD != 'undefined' && ENVIRONMENT_IS_PTHREAD) || (typeof ENVIRONMENT_IS_AUDIO_WORKLET != 'undefined' && ENVIRONMENT_IS_AUDIO_WORKLET)) Module['preRun'] = []; var necessaryPreJSTasks = Module['preRun'].slice(); - // end include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmp9cazitgs.js -// include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmpbrr8dyda.js + // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpe45adt7b.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpzebvsldf.js if (!Module['preRun']) throw 'Module.preRun should exist because file support used it; did a pre-js delete it?'; necessaryPreJSTasks.forEach((task) => { if (Module['preRun'].indexOf(task) < 0) throw 'All preRun tasks that exist before user pre-js code should remain after; did you replace Module or modify Module.preRun?'; }); - // end include: /var/folders/78/byld8t797qb4qt1fj9r1xz_w0000gn/T/tmpbrr8dyda.js + // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpzebvsldf.js -var arguments_ = []; +var programArgs = []; var thisProgram = './this.program'; var quit_ = (status, toThrow) => { throw toThrow; @@ -292,7 +290,7 @@ readAsync = async (filename, binary = true) => { thisProgram = process.argv[1].replace(/\\/g, '/'); } - arguments_ = process.argv.slice(2); + programArgs = process.argv.slice(2); quit_ = (status, toThrow) => { process.exitCode = status; @@ -360,7 +358,7 @@ var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; // perform assertions in shell.js after we set up out() and err(), as otherwise // if an assertion fails it cannot print the message -assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.'); +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time (add `shell` to `-sENVIRONMENT` to enable)'); // end include: shell.js @@ -417,44 +415,6 @@ function assert(condition, text) { var isFileURI = (filename) => filename.startsWith('file://'); // include: runtime_common.js -// include: runtime_stack_check.js -// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. -function writeStackCookie() { - var max = _emscripten_stack_get_end(); - assert((max & 3) == 0); - // If the stack ends at address zero we write our cookies 4 bytes into the - // stack. This prevents interference with SAFE_HEAP and ASAN which also - // monitor writes to address zero. - if (max == 0) { - max += 4; - } - // The stack grow downwards towards _emscripten_stack_get_end. - // We write cookies to the final two words in the stack and detect if they are - // ever overwritten. - HEAPU32[((max)>>2)] = 0x02135467; - HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; - // Also test the global address 0 for integrity. - HEAPU32[((0)>>2)] = 1668509029; -} - -function checkStackCookie() { - if (ABORT) return; - var max = _emscripten_stack_get_end(); - // See writeStackCookie(). - if (max == 0) { - max += 4; - } - var cookie1 = HEAPU32[((max)>>2)]; - var cookie2 = HEAPU32[(((max)+(4))>>2)]; - if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { - abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); - } - // Also test the global address 0 for integrity. - if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { - abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); - } -} -// end include: runtime_stack_check.js // include: runtime_exceptions.js // Base Emscripten EH error class class EmscriptenEH {} @@ -482,15 +442,31 @@ function dbg(...args) { })(); function consumedModuleProp(prop) { - if (!Object.getOwnPropertyDescriptor(Module, prop)) { - Object.defineProperty(Module, prop, { - configurable: true, - set() { - abort(`Attempt to set \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`); - + var value = Module[prop]; + var msg = `Attempt to modify \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`; + if (Array.isArray(value)) { + value = new Proxy(value, { + set(target, key, val) { + abort(msg); + return false; + }, + defineProperty(target, key, descriptor) { + abort(msg); + return false; + }, + deleteProperty(target, key) { + abort(msg); + return false; } }); } + Object.defineProperty(Module, prop, { + configurable: true, + get() { return value; }, + set() { + abort(msg); + } + }); } function makeInvalidEarlyAccess(name) { @@ -541,16 +517,68 @@ function unexportedRuntimeSymbol(sym) { } // end include: runtime_debug.js -var readyPromiseResolve, readyPromiseReject; +// include: runtime_stack_check.js +const stackCookie1 = 0x02135467; +const stackCookie2 = 0x89BACDFE; +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = stackCookie1; + HEAPU32[(((max)+(4))>>2)] = stackCookie2; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function u32ToHexString(num) { + return '0x' + (num >>> 0).toString(16).padStart(8, '0'); +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var val1 = HEAPU32[((max)>>2)]; + var val2 = HEAPU32[(((max)+(4))>>2)]; + if (val1 != stackCookie1 || val2 != stackCookie2) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords ${u32ToHexString(stackCookie2)} and ${u32ToHexString(stackCookie1)}, but received ${u32ToHexString(val2)} ${u32ToHexString(val1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} +// end include: runtime_stack_check.js // Memory management var runtimeInitialized = false; +// When ALLOW_MEMORY_GROWTH is enabled, the conversion from Wasm +// memory to ArrayBuffer requires some additional logic. +function getMemoryBuffer() { + return wasmMemory.buffer; +} + function updateMemoryViews() { - var b = wasmMemory.buffer; + // If we already have a heap that is resizeable/growable buffer we don't + // need to do anything in updateMemoryViews. + if (HEAP8?.buffer?.resizable) return; + var b = getMemoryBuffer(); HEAP8 = new Int8Array(b); HEAP16 = new Int16Array(b); HEAPU8 = new Uint8Array(b); @@ -570,11 +598,10 @@ assert(globalThis.Int32Array && globalThis.Float64Array && Int32Array.prototype. 'JS engine does not provide full typed array support'); function preRun() { - if (Module['preRun']) { - if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; - while (Module['preRun'].length) { - addOnPreRun(Module['preRun'].shift()); - } + var preRun = Module['preRun']; + if (preRun) { + if (typeof preRun == 'function') preRun = [preRun]; + onPreRuns.push(...preRun); } consumedModuleProp('preRun'); // Begin ATPRERUNS hooks @@ -598,17 +625,17 @@ TTY.init(); // Begin ATPOSTCTORS hooks FS.ignorePermissions = false; // End ATPOSTCTORS hooks + + checkStackCookie(); } function postRun() { checkStackCookie(); - // PThreads reuse the runtime from the main thread. - if (Module['postRun']) { - if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; - while (Module['postRun'].length) { - addOnPostRun(Module['postRun'].shift()); - } + var postRun = Module['postRun']; + if (postRun) { + if (typeof postRun == 'function') postRun = [postRun]; + onPostRuns.push(...postRun); } consumedModuleProp('postRun'); @@ -646,21 +673,19 @@ function abort(what) { /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); - readyPromiseReject?.(e); // Throw the error whether or not MODULARIZE is set because abort is used // in code paths apart from instantiation where an exception is expected // to be thrown when abort is called. throw e; } -function createExportWrapper(name, nargs) { +function createExportWrapper(name, func, nargs) { + assert(func); return (...args) => { assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); - var f = wasmExports[name]; - assert(f, `exported native function \`${name}\` not found`); // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); - return f(...args); + return func(...args); }; } @@ -761,8 +786,7 @@ async function createWasm() { // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup - /** @param {WebAssembly.Module=} module*/ - function receiveInstance(instance, module) { + function receiveInstance(instance) { wasmExports = instance.exports; assignWasmExports(wasmExports); @@ -795,15 +819,14 @@ async function createWasm() { // performing. // Also pthreads and wasm workers initialize the wasm instance through this // path. - if (Module['instantiateWasm']) { - return new Promise((resolve, reject) => { + var instantiateWasm = Module['instantiateWasm']; + if (instantiateWasm) { + return new Promise((resolve) => { try { - Module['instantiateWasm'](info, (inst, mod) => { - resolve(receiveInstance(inst, mod)); - }); + instantiateWasm(info, (inst) => resolve(receiveInstance(inst))); } catch(e) { err(`Module.instantiateWasm callback failed with error: ${e}`); - reject(e); + throw e; } }); } @@ -827,36 +850,15 @@ async function createWasm() { } } - /** @type {!Int16Array} */ - var HEAP16; - /** @type {!Int32Array} */ var HEAP32; - /** not-@type {!BigInt64Array} */ - var HEAP64; - /** @type {!Int8Array} */ var HEAP8; - /** @type {!Float32Array} */ - var HEAPF32; - - /** @type {!Float64Array} */ - var HEAPF64; - - /** @type {!Uint16Array} */ - var HEAPU16; - /** @type {!Uint32Array} */ var HEAPU32; - /** not-@type {!BigUint64Array} */ - var HEAPU64; - - /** @type {!Uint8Array} */ - var HEAPU8; - var callRuntimeCallbacks = (callbacks) => { while (callbacks.length > 0) { // Pass the module as the first argument. @@ -870,26 +872,6 @@ async function createWasm() { var addOnPreRun = (cb) => onPreRuns.push(cb); - - /** - * @param {number} ptr - * @param {string} type - */ - function getValue(ptr, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': return HEAP8[ptr]; - case 'i8': return HEAP8[ptr]; - case 'i16': return HEAP16[((ptr)>>1)]; - case 'i32': return HEAP32[((ptr)>>2)]; - case 'i64': return HEAP64[((ptr)>>3)]; - case 'float': return HEAPF32[((ptr)>>2)]; - case 'double': return HEAPF64[((ptr)>>3)]; - case '*': return HEAPU32[((ptr)>>2)]; - default: abort(`invalid type for getValue: ${type}`); - } - } - var noExitRuntime = true; function ptrToString(ptr) { @@ -899,27 +881,6 @@ async function createWasm() { return '0x' + ptr.toString(16).padStart(8, '0'); } - - /** - * @param {number} ptr - * @param {number} value - * @param {string} type - */ - function setValue(ptr, value, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': HEAP8[ptr] = value; break; - case 'i8': HEAP8[ptr] = value; break; - case 'i16': HEAP16[((ptr)>>1)] = value; break; - case 'i32': HEAP32[((ptr)>>2)] = value; break; - case 'i64': HEAP64[((ptr)>>3)] = BigInt(value); break; - case 'float': HEAPF32[((ptr)>>2)] = value; break; - case 'double': HEAPF64[((ptr)>>3)] = value; break; - case '*': HEAPU32[((ptr)>>2)] = value; break; - default: abort(`invalid type for setValue: ${type}`); - } - } - var stackRestore = (val) => __emscripten_stack_restore(val); var stackSave = () => _emscripten_stack_get_current(); @@ -937,6 +898,14 @@ async function createWasm() { var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + + /** + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul + * @return {number} + */ var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { var maxIdx = idx + maxBytesToRead; if (ignoreNul) return maxIdx; @@ -995,6 +964,9 @@ async function createWasm() { return str; }; + /** @type {!Uint8Array} */ + var HEAPU8; + /** * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the * emscripten HEAP, returns a copy of that string as a Javascript String object. @@ -1015,6 +987,7 @@ async function createWasm() { var ___assert_fail = (condition, filename, line, func) => abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); + class ExceptionInfo { // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. constructor(excPtr) { @@ -1073,12 +1046,16 @@ async function createWasm() { } var uncaughtExceptionCount = 0; + + var __Unwind_RaiseException = (ex) => { + assert(false, 'Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.'); + }; var ___cxa_throw = (ptr, type, destructor) => { var info = new ExceptionInfo(ptr); // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. info.init(type, destructor); uncaughtExceptionCount++; - assert(false, 'Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.'); + __Unwind_RaiseException(ptr); }; var syscallGetVarargI = () => { @@ -1156,7 +1133,7 @@ var initRandomFill = () => { // This block is not needed on v19+ since crypto.getRandomValues is builtin if (ENVIRONMENT_IS_NODE) { var nodeCrypto = require('node:crypto'); - return (view) => nodeCrypto.randomFillSync(view); + return (view) => (nodeCrypto.randomFillSync(view), 0); } return (view) => (crypto.getRandomValues(view), 0); @@ -1396,7 +1373,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } catch (e) { throw new FS.ErrnoError(29); } - if (result === undefined && bytesRead === 0) { + if (result === undefined && !bytesRead) { throw new FS.ErrnoError(6); } if (result === null || result === undefined) break; @@ -1487,6 +1464,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var mmapAlloc = (size) => { abort('internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported'); }; + var MEMFS = { ops_table:null, mount(mount) { @@ -1630,7 +1608,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return attr; }, setattr(node, attr) { - for (const key of ["mode", "atime", "mtime", "ctime"]) { + for (const key of ['mode', 'atime', 'mtime', 'ctime']) { if (attr[key] != null) { node[key] = attr[key]; } @@ -1716,10 +1694,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { node.mtime = node.ctime = Date.now(); if (canOwn) { - assert(position === 0, 'canOwn must imply no weird position inside the file'); + assert(!position, 'canOwn must imply no weird position inside the file'); node.contents = buffer.subarray(offset, offset + length); node.usedBytes = length; - } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + } else if (!node.usedBytes && !position) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. node.contents = buffer.slice(offset, offset + length); node.usedBytes = length; } else { @@ -1965,10 +1943,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; + var dependenciesPromise = null; + var resolveRunDependencies = async () => dependenciesPromise; var runDependencies = 0; - var dependenciesFulfilled = null; + var dependenciesPromiseResolve = null; var runDependencyTracking = { }; @@ -1982,21 +1962,22 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { assert(id, 'removeRunDependency requires an ID'); assert(runDependencyTracking[id]); delete runDependencyTracking[id]; - if (runDependencies == 0) { + if (!runDependencies) { if (runDependencyWatcher !== null) { clearInterval(runDependencyWatcher); runDependencyWatcher = null; } - if (dependenciesFulfilled) { - var callback = dependenciesFulfilled; - dependenciesFulfilled = null; - callback(); // can add another dependenciesFulfilled - } + dependenciesPromiseResolve(); } }; + + var addRunDependency = (id) => { + if (!runDependencies) { + dependenciesPromise = new Promise((resolve) => dependenciesPromiseResolve = resolve); + } runDependencies++; Module['monitorRunDependencies']?.(runDependencies); @@ -2004,7 +1985,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { assert(id, 'addRunDependency requires an ID') assert(!runDependencyTracking[id]); runDependencyTracking[id] = 1; - if (runDependencyWatcher === null && globalThis.setInterval) { + if (!runDependencyWatcher && globalThis.setInterval) { // Check for missing dependencies every few seconds runDependencyWatcher = setInterval(() => { if (ABORT) { @@ -2071,6 +2052,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var FS_createPreloadedFile = (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => { FS_preloadFile(parent, name, url, canRead, canWrite, dontCreateFile, canOwn, preFinish).then(onload).catch(onerror); }; + var FS = { root:null, mounts:[], @@ -2169,6 +2151,48 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { get isDevice() { return FS.isChrdev(this.mode); } + // The per-inode readiness wait-queue. The node carries a Set of listener + // entries {cb}; producers (SOCKFS, PIPEFS) call notifyListeners on a + // readiness transition, and poll()/epoll consume it. It lives on the node + // (not the fd) so dup'd fds share one queue. Only nodes that derive real + // readiness (sockets, pipes, and an epoll's own node) ever use this - + // always-ready types (regular files, ttys) never register or notify. + addListener(cb, exclusive = false) { + var entry = {cb, exclusive}; + var listeners = (this.listeners ??= new Set()); + listeners.add(entry); + return {listeners, entry}; + } + notifyListeners(flags) { + // Iterates the set without copying, which is safe ONLY under a + // load-bearing contract that every internal listener must honour: + // 1. A listener must not run user code synchronously (a poll waiter only + // resolves a Promise; an epoll registration only re-lists + + // re-notifies; the epoll callback only schedules a tick). User code + // runs on a later tick, never inside this loop. + // 2. A listener may delete entries only from ITS OWN waiter, never from + // a sibling node's set that may be mid-iteration. (Deleting an entry + // of the set being iterated here is fine - a Set tolerates removal of + // a not-yet-visited entry mid-iteration; mutating a *different* node's + // set is fine because that set is not being iterated.) + // Violating either gives silently skipped wakeups that are near-impossible + // to reproduce. Any new producer/listener must preserve it. + if (!this.listeners) return; + // Fire every non-exclusive listener. Among EPOLLEXCLUSIVE registrations + // (one fd watched by several epolls) wake only one, rotating round-robin + // per node, to avoid a thundering herd. (Only epoll registrations are ever + // exclusive; poll waiters and a node's own consumers are not.) + var excl; + for (var entry of this.listeners) { + if (entry.exclusive) (excl ||= []).push(entry); + else entry.cb(flags); + } + if (excl) { + var i = (this.exclTurn || 0) % excl.length; + this.exclTurn = i + 1; + excl[i].cb(flags); + } + } }, lookupPath(path, opts = {}) { if (!path) { @@ -2180,7 +2204,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { path = FS.cwd() + '/' + path; } - // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + // limit max consecutive symlinks to SYMLOOP_MAX. linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { // split the absolute path var parts = path.split('/').filter((p) => !!p); @@ -2472,7 +2496,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var arg = setattr ? stream : node; setattr ??= node.node_ops.setattr; FS.checkOpExists(setattr, 63) - setattr(arg, attr); + try { + setattr(arg, attr); + } catch (e) { + if (e instanceof RangeError) { + throw new FS.ErrnoError(22); + } + throw e; + } }, chrdev_stream_ops:{ open(stream) { @@ -2739,6 +2770,25 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } return parent.node_ops.symlink(parent, newname, oldpath); }, + link(oldpath, newpath, flags) { + var lookup = FS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(44); + } + var newname = PATH.basename(newpath); + var errCode = FS.mayCreate(parent, newname); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + // Hardlinks are only supported by filesystem backends that provide a + // `link` node op (e.g. NODERAWFS backed by the host). NODEFS omits it: + // a host hardlink cannot be confined to the mount root. + if (!parent.node_ops.link) { + throw new FS.ErrnoError(34); + } + return parent.node_ops.link(parent, newname, oldpath, flags); + }, rename(old_path, new_path) { var old_dirname = PATH.dirname(old_path); var new_dirname = PATH.dirname(new_path); @@ -2985,17 +3035,16 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } FS.doTruncate(stream, stream.node, len); }, - utime(path, atime, mtime) { - var lookup = FS.lookupPath(path, { follow: true }); - var node = lookup.node; - var setattr = FS.checkOpExists(node.node_ops.setattr, 63); - setattr(node, { + utime(path, atime, mtime, dontFollow) { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + FS.doSetAttr(null, lookup.node, { atime: atime, - mtime: mtime + mtime: mtime, + dontFollow }); }, open(path, flags, mode = 0o666) { - if (path === "") { + if (path === '') { throw new FS.ErrnoError(44); } flags = FS_modeStringToFlags(flags); @@ -3009,7 +3058,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { if (typeof path == 'object') { node = path; } else { - isDirPath = path.endsWith("/"); + isDirPath = path.endsWith('/'); // noent_okay makes it so that if the final component of the path // doesn't exist, lookupPath returns `node: undefined`. `path` will be // updated to point to the target of all symlinks. @@ -3092,6 +3141,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { throw new FS.ErrnoError(8); } if (stream.getdents) stream.getdents = null; // free readdir state + // The fd is going away: wake anything waiting on it (poll/epoll) with + // POLLNVAL so a blocking wait unblocks and an epoll registration is evicted + // on its next derive. Only sockets/pipes/epoll ever carry a wait-queue, so + // for every other stream (incl. nodeless noderawfs stdio) this is a no-op. + stream.node?.notifyListeners(32); try { if (stream.stream_ops.close) { stream.stream_ops.close(stream); @@ -3186,8 +3240,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // to write to file opened in read-only mode with MAP_PRIVATE flag, // as all modifications will be visible only in the memory of // the current process. - if ((prot & 2) !== 0 - && (flags & 2) === 0 + if ((prot & 2) + && !(flags & 2) && (stream.flags & 2097155) !== 2) { throw new FS.ErrnoError(2); } @@ -3216,8 +3270,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return stream.stream_ops.ioctl(stream, cmd, arg); }, readFile(path, opts = {}) { - opts.flags = opts.flags || 0; - opts.encoding = opts.encoding || 'binary'; + opts.flags = opts.flags ?? 0; + opts.encoding = opts.encoding ?? 'binary'; if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { abort(`Invalid encoding type "${opts.encoding}"`); } @@ -3233,7 +3287,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return buf; }, writeFile(path, data, opts = {}) { - opts.flags = opts.flags || 577; + opts.flags = opts.flags ?? 577; var stream = FS.open(path, opts.flags, opts.mode); data = FS_fileDataToTypedArray(data); FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); @@ -3280,7 +3334,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // use a buffer to avoid overhead of individual crypto calls per byte var randomBuffer = new Uint8Array(1024), randomLeft = 0; var randomByte = () => { - if (randomLeft === 0) { + if (!randomLeft) { randomFill(randomBuffer); randomLeft = randomBuffer.byteLength; } @@ -3496,7 +3550,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } catch (e) { throw new FS.ErrnoError(29); } - if (result === undefined && bytesRead === 0) { + if (result === undefined && !bytesRead) { throw new FS.ErrnoError(6); } if (result === null || result === undefined) break; @@ -3527,7 +3581,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { forceLoadFile(obj) { if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; if (globalThis.XMLHttpRequest) { - abort("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + abort('Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.'); } else { // Command-line. try { obj.contents = readBinary(obj.url); @@ -3558,11 +3612,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var xhr = new XMLHttpRequest(); xhr.open('HEAD', url, false); xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort(`Couldn't load ${url}. Status: ${xhr.status}`); + var datalength = Number(xhr.getResponseHeader('Content-length')); var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; - var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; + var hasByteServing = (header = xhr.getResponseHeader('Accept-Ranges')) && header === 'bytes'; + var usesGzip = (header = xhr.getResponseHeader('Content-Encoding')) && header === 'gzip'; var chunkSize = 1024*1024; // Chunk size in bytes @@ -3570,13 +3624,13 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // Function to get a range from the remote URL. var doXHR = (from, to) => { - if (from > to) abort("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) abort("only " + datalength + " bytes available! programmer error!"); + if (from > to) abort(`invalid range (${from}, ${to}) or no bytes requested!`); + if (to > datalength-1) abort(`only ${datalength} bytes available! programmer error!`); // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); + if (datalength !== chunkSize) xhr.setRequestHeader('Range', `bytes=${from}-${to}`); // Some hints to the browser that we want binary data. xhr.responseType = 'arraybuffer'; @@ -3585,11 +3639,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort("Couldn't load " + url + ". Status: " + xhr.status); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) abort(`Couldn't load ${url}. Status: ${xhr.status}`); if (xhr.response !== undefined) { return new Uint8Array(/** @type{Array} */(xhr.response || [])); } - return intArrayFromString(xhr.responseText || '', true); + return intArrayFromString(xhr.responseText ?? '', true); }; var lazyArray = this; lazyArray.setDataGetter((chunkNum) => { @@ -3608,7 +3662,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file datalength = this.getter(0).length; chunkSize = datalength; - out("LazyFiles on gzip forces download of the whole file when length is accessed"); + out('LazyFiles on gzip forces download of the whole file when length is accessed'); } this._length = datalength; @@ -3698,7 +3752,14 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }, }; + + + + + /** not-@type {!BigInt64Array} */ + var HEAP64; var SYSCALLS = { + currentUmask:18, calculateAt(dirfd, path, allowEmpty) { if (PATH.isAbs(path)) { return path; @@ -3761,7 +3822,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // MAP_PRIVATE calls need not to be synced back to underlying fs return 0; } - var buffer = HEAPU8.slice(addr, addr + len); + var buffer = HEAPU8.subarray(addr, addr + len); FS.msync(stream, buffer, offset, len, flags); }, getStreamFromFD(fd) { @@ -3774,6 +3835,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return ret; }, }; + + /** @type {!Int16Array} */ + var HEAP16; function ___syscall_fcntl64(fd, cmd, varargs) { SYSCALLS.varargs = varargs; try { @@ -3799,7 +3863,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return stream.flags; case 4: { var arg = syscallGetVarargI(); - stream.flags |= arg; + var mask = 289792; + stream.flags = (stream.flags & ~mask) | (arg & mask); return 0; } case 12: { @@ -3826,6 +3891,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + + + function ___syscall_ioctl(fd, op, varargs) { SYSCALLS.varargs = varargs; try { @@ -3931,6 +3999,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { path = SYSCALLS.getStr(path); path = SYSCALLS.calculateAt(dirfd, path); var mode = varargs ? syscallGetVarargI() : 0; + if (flags & 64) { + mode &= ~SYSCALLS.currentUmask; + } return FS.open(path, flags, mode).fd; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; @@ -3960,7 +4031,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var typeDependencies = { }; - var BindingError = class BindingError extends Error { constructor(message) { super(message); this.name = 'BindingError'; }}; + class BindingError extends Error { + constructor(message) { + super(message); + this.name = 'BindingError'; + } + } var throwBindingError = (message) => { throw new BindingError(message); }; /** @param {Object=} options */ function sharedRegisterType(rawType, registeredInstance, options = {}) { @@ -3990,6 +4066,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return sharedRegisterType(rawType, registeredInstance, options); } + + + + /** @type {!Uint16Array} */ + var HEAPU16; + + + + + /** not-@type {!BigUint64Array} */ + var HEAPU64; var integerReadValueFromPointer = (name, width, signed) => { // integers are quite common, so generate very specialized functions switch (width) { @@ -4047,10 +4134,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { name, fromWireType: fromWireType, toWireType: (destructors, value) => { - if (typeof value == "number") { + if (typeof value == 'number') { value = BigInt(value); } - else if (typeof value != "bigint") { + else if (typeof value != 'bigint') { throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${name}`); } assertIntegerRange(name, value, minRange, maxRange); @@ -4062,6 +4149,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; + /** @suppress {globalThis} */ var __embind_register_bool = (rawType, name, trueValue, falseValue) => { name = AsciiToString(name); @@ -4158,7 +4246,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return registeredInstances[ptr]; }; - var InternalError = class InternalError extends Error { constructor(message) { super(message); this.name = 'InternalError'; }}; + class InternalError extends Error { + constructor(message) { + super(message); + this.name = 'InternalError'; + } + } var throwInternalError = (message) => { throw new InternalError(message); }; var makeClassHandle = (prototype, record) => { @@ -4278,10 +4371,10 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // This is more useful than the empty stacktrace of `FinalizationRegistry` // callback. var cls = $$.ptrType.registeredClass; - var err = new Error(`Embind found a leaked C++ instance ${cls.name} <${ptrToString($$.ptr)}>.\n` + - "We'll free it automatically in this case, but this functionality is not reliable across various environments.\n" + - "Make sure to invoke .delete() manually once you're done with the instance instead.\n" + - "Originally allocated"); // `.stack` will add "at ..." after this sentence + var err = new Error(`Embind found a leaked C++ instance ${cls.name} <${ptrToString($$.ptr)}>. +We'll free it automatically in this case, but this functionality is not reliable across various environments. +Make sure to invoke .delete() manually once you're done with the instance instead. +Originally allocated`); // `.stack` will add "at ..." after this sentence if ('captureStackTrace' in Error) { Error.captureStackTrace(err, RegisteredPointer_fromWireType); } @@ -4311,7 +4404,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { let proto = ClassHandle.prototype; Object.assign(proto, { - "isAliasOf"(other) { + 'isAliasOf'(other) { if (!(this instanceof ClassHandle)) { return false; } @@ -4338,7 +4431,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return leftClass === rightClass && left === right; }, - "clone"() { + 'clone'() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } @@ -4359,7 +4452,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }, - "delete"() { + 'delete'() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } @@ -4377,11 +4470,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }, - "isDeleted"() { + 'isDeleted'() { return !this.$$.ptr; }, - "deleteLater"() { + 'deleteLater'() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } @@ -4716,11 +4809,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); } /** @suppress {checkTypes} */ - assert(wasmTable.get(funcPtr) == func, 'JavaScript-side Wasm function table mirror is out of date!'); + assert(wasmTable.get(funcPtr) == func, 'table mirror is out of date'); return func; }; var embind__requireFunction = (signature, rawFunction, isAsync = false) => { - assert(!isAsync, 'Async bindings are only supported with JSPI.'); + assert(!isAsync, 'async bindings are only supported with JSPI'); signature = AsciiToString(signature); @@ -4968,19 +5061,19 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { argsList.push(`arg${i}`) argsListWired.push(`arg${i}Wired`) } - argsList = argsList.join(',') - argsListWired = argsListWired.join(',') + argsList = argsList.join() + argsListWired = argsListWired.join() var invokerFnBody = `return function (${argsList}) {\n`; - invokerFnBody += "checkArgCount(arguments.length, minArgs, maxArgs, humanName, throwBindingError);\n"; + invokerFnBody += 'checkArgCount(arguments.length, minArgs, maxArgs, humanName, throwBindingError);\n'; if (needsDestructorStack) { - invokerFnBody += "var destructors = [];\n"; + invokerFnBody += 'var destructors = [];\n'; } - var dtorStack = needsDestructorStack ? "destructors" : "null"; - var args1 = ["humanName", "throwBindingError", "invoker", "fn", "runDestructors", "fromRetWire", "toClassParamWire"]; + var dtorStack = needsDestructorStack ? 'destructors' : 'null'; + var args1 = ['humanName', 'throwBindingError', 'invoker', 'fn', 'runDestructors', 'fromRetWire', 'toClassParamWire']; if (isClassMethodFunc) { invokerFnBody += `var thisWired = toClassParamWire(${dtorStack}, this);\n`; @@ -4992,15 +5085,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { args1.push(argName); } - invokerFnBody += (returns || isAsync ? "var rv = ":"") + `invoker(${argsListWired});\n`; + invokerFnBody += (returns || isAsync ? 'var rv = ' : '') + `invoker(${argsListWired});\n`; - var returnVal = returns ? "rv" : ""; + var returnVal = returns ? 'rv' : ''; if (needsDestructorStack) { - invokerFnBody += "runDestructors(destructors);\n"; + invokerFnBody += 'runDestructors(destructors);\n'; } else { for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); + var paramName = (i === 1 ? 'thisWired' : `arg${i - 2}Wired`); if (argTypes[i].destructorFunction !== null) { invokerFnBody += `${paramName}_dtor(${paramName});\n`; args1.push(`${paramName}_dtor`); @@ -5009,12 +5102,12 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } if (returns) { - invokerFnBody += "var ret = fromRetWire(rv);\n" + - "return ret;\n"; + invokerFnBody += 'var ret = fromRetWire(rv);\n' + + 'return ret;\n'; } else { } - invokerFnBody += "}\n"; + invokerFnBody += '}\n'; args1.push('checkArgCount', 'minArgs', 'maxArgs'); invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`; @@ -5045,15 +5138,15 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var argCount = argTypes.length; if (argCount < 2) { - throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); + throwBindingError('argTypes array size mismatch! Must at least get return value and receiver (this) types!'); } - assert(!isAsync, 'Async bindings are only supported with JSPI.'); + assert(!isAsync, 'async bindings are only supported with JSPI'); var isClassMethodFunc = (argTypes[1] !== null && classType !== null); // Free functions with signature "void function()" do not need an invoker that marshalls between wire types. // TODO: This omits argument count check - enable only at -O3 or similar. - // if (ENABLE_UNSAFE_OPTS && argCount == 2 && argTypes[0].name == "void" && !isClassMethodFunc) { + // if (ENABLE_UNSAFE_OPTS && argCount == 2 && argTypes[0].name == 'void' && !isClassMethodFunc) { // return FUNCTION_TABLE[fn]; // } @@ -5134,9 +5227,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var getFunctionName = (signature) => { signature = signature.trim(); - const argsIndex = signature.indexOf("("); + const argsIndex = signature.indexOf('('); if (argsIndex === -1) return signature; - assert(signature.endsWith(")"), "Parentheses for argument names should match."); + assert(signature.endsWith(')'), 'Parentheses for argument names should match.'); return signature.slice(0, argsIndex); }; var __embind_register_class_function = (rawClassType, @@ -5158,7 +5251,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { classType = classType[0]; var humanName = `${classType.name}.${methodName}`; - if (methodName.startsWith("@@")) { + if (methodName.startsWith('@@')) { methodName = Symbol[methodName.substring(2)]; } @@ -5308,7 +5401,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var emval_handles = [0,1,,1,null,1,true,1,false,1]; var __emval_decref = (handle) => { if (handle > 9 && 0 === --emval_handles[handle + 1]) { - assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`); + assert(emval_handles[handle] !== undefined, `decref for unallocated handle`); var value = emval_handles[handle]; emval_handles[handle] = undefined; emval_freelist.push(handle); @@ -5359,6 +5452,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var __embind_register_emval = (rawType) => registerType(rawType, EmValType); + + + + + var enumReadValueFromPointer = (name, width, signed) => { switch (width) { case 1: return signed ? @@ -5379,7 +5477,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { function getEnumValueType(rawValueType) { // This must match the values of enum_value_type in wire.h - return rawValueType === 0 ? 'object' : (rawValueType === 1 ? 'number' : 'string'); + return !rawValueType ? 'object' : (rawValueType === 1 ? 'number' : 'string'); } /** @suppress {globalThis} */ var __embind_register_enum = (rawType, name, size, isSigned, rawValueType) => { @@ -5492,6 +5590,11 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { } }; + /** @type {!Float32Array} */ + var HEAPF32; + + /** @type {!Float64Array} */ + var HEAPF64; var floatReadValueFromPointer = (name, width) => { switch (width) { case 4: return function(pointer) { @@ -5513,7 +5616,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { name, fromWireType: (value) => value, toWireType: (destructors, value) => { - if (typeof value != "number" && typeof value != "boolean") { + if (typeof value != 'number' && typeof value != 'boolean') { throw new TypeError(`Cannot convert ${embindRepr(value)} to ${name}`); } // The VM will perform JS to Wasm value conversion, according to the spec: @@ -5572,7 +5675,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { name, fromWireType: fromWireType, toWireType: (destructors, value) => { - if (typeof value != "number" && typeof value != "boolean") { + if (typeof value != 'number' && typeof value != 'boolean') { throw new TypeError(`Cannot convert "${embindRepr(value)}" to ${name}`); } assertIntegerRange(name, value, minRange, maxRange); @@ -5586,6 +5689,8 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; + + var __embind_register_memory_view = (rawType, dataTypeIndex, name) => { var typeMapping = [ Int8Array, @@ -5622,14 +5727,17 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { - assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8 requires a third parameter that specifies the length of the output buffer'); return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); }; + + var __embind_register_std_string = (rawType, name) => { name = AsciiToString(name); var stdStringIsUTF8 = true; @@ -5712,8 +5820,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var UTF16Decoder = globalThis.TextDecoder ? new TextDecoder('utf-16le') : undefined;; + var UTF16ToString = (ptr, maxBytesToRead, ignoreNul) => { - assert(ptr % 2 == 0, 'Pointer passed to UTF16ToString must be aligned to two bytes!'); + assert(ptr % 2 == 0, 'pointer passed to UTF16ToString must be 2-byte aligned'); var idx = ((ptr)>>1); var endIdx = findStringEnd(HEAPU16, idx, maxBytesToRead / 2, ignoreNul); @@ -5737,11 +5846,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return str; }; - var stringToUTF16 = (str, outPtr, maxBytesToWrite) => { - assert(outPtr % 2 == 0, 'Pointer passed to stringToUTF16 must be aligned to two bytes!'); - assert(typeof maxBytesToWrite == 'number', 'stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); - // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed. - maxBytesToWrite ??= 0x7FFFFFFF; + var stringToUTF16 = (str, outPtr, maxBytesToWrite = 0x7FFFFFFF) => { + assert(outPtr % 2 == 0, 'pointer passed to stringToUTF16 must be 2-byte aligned'); + assert(typeof maxBytesToWrite == 'number', 'stringToUTF16 requires a third parameter that specifies the length of the output buffer'); if (maxBytesToWrite < 2) return 0; maxBytesToWrite -= 2; // Null terminator. var startPtr = outPtr; @@ -5760,7 +5867,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { var lengthBytesUTF16 = (str) => str.length*2; var UTF32ToString = (ptr, maxBytesToRead, ignoreNul) => { - assert(ptr % 4 == 0, 'Pointer passed to UTF32ToString must be aligned to four bytes!'); + assert(ptr % 4 == 0, 'pointer passed to UTF32ToString must be 2-byte aligned'); var str = ''; var startIdx = ((ptr)>>2); // If maxBytesToRead is not passed explicitly, it will be undefined, and this @@ -5773,11 +5880,9 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return str; }; - var stringToUTF32 = (str, outPtr, maxBytesToWrite) => { - assert(outPtr % 4 == 0, 'Pointer passed to stringToUTF32 must be aligned to four bytes!'); - assert(typeof maxBytesToWrite == 'number', 'stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); - // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed. - maxBytesToWrite ??= 0x7FFFFFFF; + var stringToUTF32 = (str, outPtr, maxBytesToWrite = 0x7FFFFFFF) => { + assert(outPtr % 4 == 0, 'pointer passed to stringToUTF32 must be 4-byte aligned'); + assert(typeof maxBytesToWrite == 'number', 'stringToUTF32 requires a third parameter that specifies the length of the output buffer'); if (maxBytesToWrite < 4) return 0; var startPtr = outPtr; var endPtr = startPtr + maxBytesToWrite - 4; @@ -5811,6 +5916,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return len; }; + var __embind_register_std_wstring = (rawType, charSize, name) => { name = AsciiToString(name); var decodeString, encodeString, lengthBytesUTF; @@ -5878,6 +5984,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { return id; }; + var emval_lookupTypes = (argCount, argTypes) => { var a = new Array(argCount); for (var i = 0; i < argCount; ++i) { @@ -5888,6 +5995,7 @@ var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { }; + var emval_returnValue = (toReturnWire, destructorsRef, handle) => { var destructors = []; var result = toReturnWire(destructors, handle); @@ -5999,7 +6107,7 @@ ${functionBody} 2147483648; var alignMemory = (size, alignment) => { - assert(alignment, "alignment argument is required"); + assert(alignment, 'alignment argument is required'); return Math.ceil(size / alignment) * alignment; }; @@ -6014,9 +6122,10 @@ ${functionBody} } catch(e) { err(`growMemory: Attempted to grow heap from ${oldHeapSize} bytes to ${size} bytes, but got error: ${e}`); } - // implicit 0 return to save code size (caller will cast "undefined" into 0 + // implicit 0 return to save code size (caller will cast 'undefined' into 0 // anyhow) }; + var _emscripten_resize_heap = (requestedSize) => { var oldSize = HEAPU8.length; // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. @@ -6092,7 +6201,6 @@ ${functionBody} // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down if (keepRuntimeAlive() && !implicit) { var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; - readyPromiseReject?.(msg); err(msg); } @@ -6113,6 +6221,7 @@ ${functionBody} } + /** @param {number=} offset */ var doReadv = (stream, iov, iovcnt, offset) => { var ret = 0; @@ -6120,7 +6229,18 @@ ${functionBody} var ptr = HEAPU32[((iov)>>2)]; var len = HEAPU32[(((iov)+(4))>>2)]; iov += 8; - var curr = FS.read(stream, HEAP8, ptr, len, offset); + try { + var curr = FS.read(stream, HEAP8, ptr, len, offset); + } catch (e) { + // On a non-blocking stream a subsequent read may would-block after we + // already gathered data. POSIX readv is a single gather-read: return + // what we have rather than failing the whole call. + if (ret > 0 && e instanceof FS.ErrnoError && + (e.errno == 6 || e.errno == 6)) { + break; + } + throw e; + } if (curr < 0) return -1; ret += curr; if (curr < len) break; // nothing more to read @@ -6131,6 +6251,7 @@ ${functionBody} return ret; }; + function _fd_read(fd, iov, iovcnt, pnum) { try { @@ -6150,17 +6271,18 @@ ${functionBody} var INT53_MIN = -9007199254740992; var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); + function _fd_seek(fd, offset, whence, newOffset) { offset = bigintToI53Checked(offset); try { - if (isNaN(offset)) return 61; + if (isNaN(offset)) return 22; var stream = SYSCALLS.getStreamFromFD(fd); FS.llseek(stream, offset, whence); HEAP64[((newOffset)>>3)] = BigInt(stream.position); - if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; // reset readdir state + if (stream.getdents && !offset && whence === 0) stream.getdents = null; // reset readdir state return 0; } catch (e) { if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; @@ -6169,27 +6291,34 @@ ${functionBody} ; } + + /** @param {number=} offset */ var doWritev = (stream, iov, iovcnt, offset) => { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { + // Gather all iovecs into one contiguous buffer and issue a single + // FS.write, matching POSIX writev's single gather-write semantics (as + // __syscall_sendmsg already does). Per-iovec writes fragment a stream + // socket send into multiple segments, breaking stream byte semantics. + if (iovcnt == 1) { + // Single iovec: write directly from HEAP8, no gather buffer needed. + return FS.write(stream, HEAP8, HEAPU32[((iov)>>2)], HEAPU32[(((iov)+(4))>>2)], offset); + } + var total = 0; + for (var i = 0, p = iov; i < iovcnt; i++, p += 8) { + total += HEAPU32[(((p)+(4))>>2)]; + } + var view = new Uint8Array(total); + var voff = 0; + for (var i = 0; i < iovcnt; i++, iov += 8) { var ptr = HEAPU32[((iov)>>2)]; var len = HEAPU32[(((iov)+(4))>>2)]; - iov += 8; - var curr = FS.write(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (curr < len) { - // No more space to write. - break; - } - if (typeof offset != 'undefined') { - offset += curr; - } + view.set(HEAPU8.subarray(ptr, ptr + len), voff); + voff += len; } - return ret; + return FS.write(stream, view, 0, total, offset); }; + function _fd_write(fd, iov, iovcnt, pnum) { try { @@ -6235,7 +6364,7 @@ assert(emval_handles.length === 5 * 2); // Begin ATMODULES hooks if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; -if (Module['preloadPlugins']) preloadPlugins = Module['preloadPlugins']; + if (Module['print']) out = Module['print']; if (Module['printErr']) err = Module['printErr']; if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; @@ -6243,7 +6372,7 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; checkIncomingModuleAPI(); - if (Module['arguments']) arguments_ = Module['arguments']; + if (Module['arguments']) programArgs = Module['arguments']; if (Module['thisProgram']) thisProgram = Module['thisProgram']; // Assertions on removed incoming Module JS APIs. @@ -6262,10 +6391,13 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; assert(typeof Module['wasmMemory'] == 'undefined', 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); assert(typeof Module['INITIAL_MEMORY'] == 'undefined', 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); - if (Module['preInit']) { - if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; - while (Module['preInit'].length > 0) { - Module['preInit'].shift()(); + var preInit = Module['preInit']; + if (preInit) { + if (typeof preInit == 'function') Module['preInit'] = preInit = [preInit]; + // Written as a loop so that preInit functions that themselves add more + // preInit functions. Is this actually needed? + while (preInit.length > 0) { + preInit.shift()(); } } consumedModuleProp('preInit'); @@ -6331,6 +6463,8 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; 'getFunctionAddress', 'addFunction', 'removeFunction', + 'setValue', + 'getValue', 'intArrayToString', 'stringToAscii', 'stringToNewUTF8', @@ -6354,12 +6488,14 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; 'registerOrientationChangeEventCallback', 'fillFullscreenChangeEventData', 'registerFullscreenChangeEventCallback', + 'callCanvasResizedCallback', 'JSEvents_requestFullscreen', 'JSEvents_resizeCanvasForFullscreen', 'registerRestoreOldStyle', 'hideEverythingExceptGivenElement', 'restoreHiddenElements', 'setLetterbox', + 'currentFullscreenStrategy', 'softFullscreenResizeWebGLRenderTarget', 'doRequestFullscreen', 'fillPointerlockChangeEventData', @@ -6391,6 +6527,7 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; 'registerPreMainLoop', 'getPromise', 'makePromise', + 'addPromise', 'idsToPromises', 'makePromiseCallback', 'findMatchingCatch', @@ -6419,6 +6556,7 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; 'colorChannelsInGlTextureFormat', 'emscriptenWebGLGetTexPixelData', 'emscriptenWebGLGetUniform', + 'webglGetProgramUniformLocation', 'webglGetUniformLocation', 'webglPrepareUniformLocationsBeforeFirstUse', 'webglGetLeftBracePos', @@ -6427,9 +6565,6 @@ if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; 'writeGLArray', 'registerWebGlEventCallback', 'runAndAbortIfError', - 'ALLOC_NORMAL', - 'ALLOC_STACK', - 'allocate', 'writeStringToMemory', 'writeAsciiToMemory', 'allocateUTF8', @@ -6498,8 +6633,6 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'addOnPostRun', 'freeTableIndexes', 'functionsInTableMap', - 'setValue', - 'getValue', 'PATH', 'PATH_FS', 'UTF8Decoder', @@ -6520,7 +6653,6 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'JSEvents', 'specialHTMLTargets', 'findCanvasEventTarget', - 'currentFullscreenStrategy', 'restoreOldWindowedStyle', 'UNWIND_CACHE', 'ExitStatus', @@ -6537,7 +6669,6 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'ExceptionInfo', 'Browser', 'requestFullscreen', - 'requestFullScreen', 'setCanvasSize', 'getUserMedia', 'createContext', @@ -6618,6 +6749,7 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'FS_mkdir', 'FS_mkdev', 'FS_symlink', + 'FS_link', 'FS_rename', 'FS_rmdir', 'FS_readdir', @@ -6772,6 +6904,25 @@ function checkIncomingModuleAPI() { ignoredModuleProp('onRealloc'); ignoredModuleProp('onFree'); ignoredModuleProp('onSbrkGrow'); + ignoredModuleProp('onCOSCacheHit'); + ignoredModuleProp('onCOSCacheMiss'); + ignoredModuleProp('onCOSStore'); + ignoredModuleProp('GL_MAX_TEXTURE_IMAGE_UNITS'); + ignoredModuleProp('SDL_canPlayWithWebAudio'); + ignoredModuleProp('SDL_numSimultaneouslyQueuedBuffers'); + ignoredModuleProp('freePreloadedMediaOnUse'); + ignoredModuleProp('preinitializedWebGLContext'); + ignoredModuleProp('keyboardListeningElement'); + ignoredModuleProp('doNotCaptureKeyboard'); + ignoredModuleProp('extraStackTrace'); + ignoredModuleProp('preloadPlugins'); + ignoredModuleProp('preMainLoop'); + ignoredModuleProp('postMainLoop'); + ignoredModuleProp('forcedAspectRatio'); + ignoredModuleProp('mainScriptUrlOrBlob'); + ignoredModuleProp('onFullScreen'); + ignoredModuleProp('INITIAL_MEMORY'); + ignoredModuleProp('wasmMemory'); } // Imports from the Wasm binary. @@ -6807,13 +6958,13 @@ function assignWasmExports(wasmExports) { assert(typeof wasmExports['emscripten_stack_get_current'] != 'undefined', 'missing Wasm export: emscripten_stack_get_current'); assert(typeof wasmExports['memory'] != 'undefined', 'missing Wasm export: memory'); assert(typeof wasmExports['__indirect_function_table'] != 'undefined', 'missing Wasm export: __indirect_function_table'); - ___getTypeName = createExportWrapper('__getTypeName', 1); - _malloc = createExportWrapper('malloc', 1); - _free = createExportWrapper('free', 1); - _fflush = createExportWrapper('fflush', 1); + ___getTypeName = createExportWrapper('__getTypeName', wasmExports['__getTypeName'], 1); + _malloc = createExportWrapper('malloc', wasmExports['malloc'], 1); + _free = createExportWrapper('free', wasmExports['free'], 1); + _fflush = createExportWrapper('fflush', wasmExports['fflush'], 1); _emscripten_stack_get_end = wasmExports['emscripten_stack_get_end']; _emscripten_stack_get_base = wasmExports['emscripten_stack_get_base']; - _strerror = createExportWrapper('strerror', 1); + _strerror = createExportWrapper('strerror', wasmExports['strerror'], 1); _emscripten_stack_init = wasmExports['emscripten_stack_init']; _emscripten_stack_get_free = wasmExports['emscripten_stack_get_free']; __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; @@ -6919,54 +7070,37 @@ function stackCheckInit() { writeStackCookie(); } -function run() { - - if (runDependencies > 0) { - dependenciesFulfilled = run; - return; - } +async function run() { + assert(!calledRun); + calledRun = true; stackCheckInit(); preRun(); - // a preRun added a dependency, run will be called later - if (runDependencies > 0) { - dependenciesFulfilled = run; - return; + if (runDependencies) { + await resolveRunDependencies(); } - function doRun() { - // run may have just been called through dependencies being fulfilled just in this very frame, - // or while the async setStatus time below was happening - assert(!calledRun); - calledRun = true; - Module['calledRun'] = true; - - if (ABORT) return; + var setStatus = Module['setStatus']; + if (setStatus) { + setStatus('Running...'); + // Yield to the event loop to allow the browser to paint "Running..." + await new Promise((resolve) => setTimeout(resolve, 1)); + // Then we want to clear the status text, but only after the rest of this function runs. + setTimeout(setStatus, 1, ''); + } - initRuntime(); + if (ABORT) return; - readyPromiseResolve?.(Module); - Module['onRuntimeInitialized']?.(); - consumedModuleProp('onRuntimeInitialized'); + initRuntime(); - assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'); + Module['onRuntimeInitialized']?.(); + consumedModuleProp('onRuntimeInitialized'); - postRun(); - } + assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'); - if (Module['setStatus']) { - Module['setStatus']('Running...'); - setTimeout(() => { - setTimeout(() => Module['setStatus'](''), 1); - doRun(); - }, 1); - } else - { - doRun(); - } - checkStackCookie(); + postRun(); } function checkUnflushedContent() { @@ -7012,28 +7146,14 @@ var wasmExports; // In modularize mode the generated code is within a factory function so we // can use await here (since it's not top-level-await). -wasmExports = await (createWasm()); - -run(); +wasmExports = await createWasm(); +await run(); // end include: postamble.js // include: postamble_modularize.js // In MODULARIZE mode we wrap the generated code in a factory function // and return either the Module itself, or a promise of the module. -// -// We assign to the `moduleRtn` global here and configure closure to see -// this as an extern so it won't get minified. - -if (runtimeInitialized) { - moduleRtn = Module; -} else { - // Set up the promise that indicates the Module is initialized - moduleRtn = new Promise((resolve, reject) => { - readyPromiseResolve = resolve; - readyPromiseReject = reject; - }); -} // Assertion for attempting to access module properties on the incoming // moduleArg. In the past we used this object as the prototype of the module @@ -7054,7 +7174,7 @@ for (const prop of Object.keys(Module)) { - return moduleRtn; + return Module; } // Export using a UMD style export, or ES6 exports if selected diff --git a/apps/micropolis/src/lib/micropolisengine.wasm b/apps/micropolis/src/lib/micropolisengine.wasm index f991f6c39944b2352d7d811d9fbbf92c18c36396..1082bf892d73f0ba2c5a8466344b73845415ef99 100755 GIT binary patch delta 35347 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 delta 32527 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 Date: Tue, 18 Aug 2026 12:53:25 +0100 Subject: [PATCH 3/4] Add `make debug` target for an instrumented engine build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a second build variant for hunting memory bugs in the C++ engine, such as the out-of-bounds trap that apps/micropolis/cli/simCrash.test.ts reproduces. The release build only yields raw offsets ("wasm-function[2874]:0x63730"), which is not enough to locate a fault. source ~/fri/emsdk/emsdk_env.sh make debug # -> build/debug/, ~11s from clean Flags: -g -O0 for DWARF and un-inlined frames, plus ASSERTIONS=2, SAFE_HEAP=1, STACK_OVERFLOW_CHECK=2 and DISABLE_EXCEPTION_CATCHING=0. SAFE_HEAP is the valuable one — it traps on the first bad access instead of wherever the corruption later surfaces. DEMANGLE_SUPPORT is deliberately omitted: it has been removed from Emscripten and now hard-errors the link; -g provides demangled names anyway. Structure, so the two variants cannot contaminate each other: - objects go to build/obj/{release,debug}, so switching targets can never link a mix of differently-instrumented objects - debug output goes to build/debug/, never build/ - `make install` refuses under BUILD=debug (guard evaluated at parse time, before `all` links anything), so an instrumented engine cannot be installed over the artifacts the app ships - `clean` removes both variants, and sweeps stale src/*.o left by the previous makefile layout Release behavior is unchanged: after this commit `make` still writes the same paths and produces a micropolisengine.wasm byte-identical to the committed artifact. SAFE_HEAP makes the debug engine roughly an order of magnitude slower, which is why it stays out of the default target. Already earned its keep — it turns the kobe crash into a readable chain: doSpecialZone -> doAirport -> generateCopter -> makeSprite -> newSprite -> std::string::operator= -> segfault. Co-Authored-By: Claude Opus 5 (1M context) --- packages/micropolis-engine/makefile | 93 ++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 7 deletions(-) diff --git a/packages/micropolis-engine/makefile b/packages/micropolis-engine/makefile index 98d14aad..304e8eae 100644 --- a/packages/micropolis-engine/makefile +++ b/packages/micropolis-engine/makefile @@ -96,6 +96,46 @@ EMLDFLAGS = \ --preload-file ../../content/micropolis/cities@cities/ +######################################################################## +# Debug build settings +# +# `make debug` builds a separate, instrumented engine for hunting memory bugs +# (e.g. the out-of-bounds trap in simTick() that apps/micropolis/cli/ +# simCrash.test.ts reproduces). Release and debug never share object files — +# they compile to build/obj/release and build/obj/debug — so switching targets +# cannot silently link a mix of differently-instrumented objects. +# +# Flags, and why each one: +# -g full DWARF; turns wasm-function[2874] into file:line +# -O0 no inlining, so frames map to real C++ functions +# -s ASSERTIONS=2 enables the runtime's own sanity checks, verbosely +# -s SAFE_HEAP=1 traps on the *first* misaligned/out-of-bounds access +# rather than wherever the corruption later surfaces — +# this is the flag that localizes the bug +# -s STACK_OVERFLOW_CHECK=2 catches stack smashes with a real trace +# -s DISABLE_EXCEPTION_CATCHING=0 let asserts/throws surface as exceptions +# +# Deliberately absent: DEMANGLE_SUPPORT. It was removed from Emscripten (see +# tools/settings.py's removed-settings table) and now hard-errors the link. +# Demangled C++ names come from -g instead. +# +# SAFE_HEAP makes the engine roughly an order of magnitude slower. That is the +# point of keeping it out of the default target. + +EMDEBUG_CXXFLAGS = \ + -g \ + -O0 \ + -fno-omit-frame-pointer + +EMDEBUG_LDFLAGS = \ + -g \ + -O0 \ + -s ASSERTIONS=2 \ + -s SAFE_HEAP=1 \ + -s STACK_OVERFLOW_CHECK=2 \ + -s DISABLE_EXCEPTION_CATCHING=0 + + ######################################################################## # Sources and headers @@ -146,8 +186,28 @@ HEADERS = \ ######################################################################## -OBJECTS = $(SOURCES:.cpp=.o) -LIBRARY = build/micropolisengine +# BUILD selects the variant: "release" (default) or "debug". `make debug` just +# re-invokes make with BUILD=debug, so both variants share one set of rules. +BUILD ?= release + +OBJDIR = build/obj/$(BUILD) +OBJECTS = $(patsubst src/%.cpp,$(OBJDIR)/%.o,$(SOURCES)) + +# Release keeps writing to build/ so `make install` and every existing +# reference to build/micropolisengine.* behave exactly as before. Debug lands +# in build/debug/ so an instrumented engine can never be mistaken for, or +# installed over, the shipping artifacts. +ifeq ($(BUILD),debug) +OUTDIR = build/debug +VARIANT_CXXFLAGS = $(EMDEBUG_CXXFLAGS) +VARIANT_LDFLAGS = $(EMDEBUG_LDFLAGS) +else +OUTDIR = build +VARIANT_CXXFLAGS = +VARIANT_LDFLAGS = +endif + +LIBRARY = $(OUTDIR)/micropolisengine OUTPUTFILES = \ $(LIBRARY).wasm \ $(LIBRARY).js \ @@ -155,22 +215,39 @@ OUTPUTFILES = \ $(LIBRARY).data \ $(LIBRARY).html +.PHONY: all debug install clean + all: $(LIBRARY) +# Build the instrumented engine into build/debug/. Never installed by +# `make install` — see the debug settings section above for the flag rationale. +debug: + $(MAKE) BUILD=debug all + # Compilation rule for wasm $(LIBRARY): $(OBJECTS) - mkdir -p build + mkdir -p $(OUTDIR) $(EMCC) \ $(EMLDFLAGS) \ + $(VARIANT_LDFLAGS) \ -o $(LIBRARY).html \ $^ -%.o: %.cpp $(HEADERS) +$(OBJDIR)/%.o: src/%.cpp $(HEADERS) + mkdir -p $(OBJDIR) $(EMCC) \ $(EMCXXFLAGS) \ + $(VARIANT_CXXFLAGS) \ -c $< \ -o $@ +# Installs the release artifacts only; refuses to run under BUILD=debug so an +# instrumented engine never overwrites what the app ships. The guard is at +# parse time (not a recipe line) so it fires before `all` does any linking. +ifeq ($(BUILD),debug) +install: + $(error refusing to install a debug build over the release artifacts; run `make install` without BUILD=debug) +else install: all echo "MicropolisEngine: makefile: make install" pwd @@ -180,12 +257,14 @@ install: all cp build/micropolisengine.data ../../apps/micropolis/src/lib { printf '%s\n' '// @ts-nocheck'; cat build/micropolisengine.js; } > ../../apps/micropolis/src/lib/micropolisengine.js if [ -f build/micropolisengine.d.ts ]; then cp build/micropolisengine.d.ts ../../apps/micropolis/src/types; else echo "micropolisengine.d.ts was not generated; keeping existing TypeScript bindings"; fi +endif +# Removes both variants' objects and outputs. Also sweeps any src/*.o left by +# an older revision of this makefile, which compiled objects next to sources. clean: rm -rf \ - $(OBJECTS) \ - $(OUTPUTFILES) \ - build + build \ + src/*.o ######################################################################## From 9a28656653bf60de309a686082aac5f038aa4309 Mon Sep 17 00:00:00 2001 From: Fabio Rocha Date: Tue, 18 Aug 2026 13:12:50 +0100 Subject: [PATCH 4/4] Fix out-of-bounds crash: construct SimSprite; init Micropolis::callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two uninitialized-memory bugs of the same kind — a non-trivial C++ member reached without ever being constructed, which survives only while the heap happens to be zero-filled. 1. sprite.cpp: Micropolis::newSprite() allocated SimSprite with newPtr() (malloc) and then did `sprite->name = name`. SimSprite has a std::string member, so that assignment dereferenced whatever garbage the string's internal pointers held. Now allocated with `new SimSprite()`. Sprites are pooled on freeSprites and never released (see destroySprite), so there is deliberately no matching delete — the lifecycle is unchanged. This was the out-of-bounds trap the crash sweep reproduced. The debug build localized it precisely: doSpecialZone -> doAirport -> generateCopter -> makeSprite -> newSprite -> std::string::operator= -> segfault It needs an airport to spawn a helicopter, which is why city maps without one never hit it. 2. micropolis.cpp: `callback` was never initialized — not by the constructor, not by init() — yet setCallback() does `if (callback != NULL) delete callback`. A first Micropolis worked only because a fresh WASM heap is zeroed; a second one allocated over used memory deleted a stale pointer and trapped with "table index is out of bounds". Now initialized to NULL in the constructor, listed first to match its declaration order in micropolis.h (verified clean under -Wreorder). Verification: - crash sweep: 64/64 pass (was 3 failing). Widened to 4 seeds x 480 turns: 128/128 pass. - debug build (SAFE_HEAP=1, ASSERTIONS=2) runs kobe to completion; it segfaulted before. - live-clock quick mode passes 6/6; it failed ~3/8 of runs before. - five sequential Micropolis instances with delete() between them all succeed — the exact pattern that used to trap on the second one. - full app suite: 140 passed, only the 4 pre-existing monorepo.integration failures that need a prior `pnpm build`. simCrash.test.ts's header is updated to record what it caught and why it stays: KNOWN_CRASHERS becomes FORMER_CRASHERS, still labelled in the output so a regression is recognizable as this bug returning. Engine artifacts are rebuilt; the micropolisengine.js diff is only embedded build-temp paths in comments. Co-Authored-By: Claude Opus 5 (1M context) --- apps/micropolis/cli/simCrash.test.ts | 36 +++++++++--------- apps/micropolis/src/lib/micropolisengine.js | 12 +++--- apps/micropolis/src/lib/micropolisengine.wasm | Bin 439159 -> 439256 bytes packages/micropolis-engine/src/micropolis.cpp | 7 ++++ packages/micropolis-engine/src/sprite.cpp | 12 +++++- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apps/micropolis/cli/simCrash.test.ts b/apps/micropolis/cli/simCrash.test.ts index f8b474d2..f51e28fe 100644 --- a/apps/micropolis/cli/simCrash.test.ts +++ b/apps/micropolis/cli/simCrash.test.ts @@ -41,17 +41,19 @@ * SIM_CRASH_SEEDS=a,b seeds to sweep (default 1,42) * SIM_CRASH_LIVE_CLOCK=1 don't freeze the clock — reproduces the flakiness * - * DEFAULTS ARE CHOSEN TO ACTUALLY CATCH THE BUG + * WHAT THIS ORIGINALLY CAUGHT (now fixed — keep as regression coverage) * - * Measured against the current engine, under a frozen clock: - * - kobe traps at every seed tried (1, 42, 1234, 99999), from 30 turns up. - * - haight traps only at seed 1, and only once past ~240 turns. - * - deadwood — the originally reported crasher — survives all four seeds even - * at 240 turns, so its crash needs a starting world none of these seeds - * reach. It stays listed in KNOWN_CRASHERS as a reminder, not a skip. - * So this is not one city's bug: it is a latent out-of-bounds that longer runs - * surface in more cities. 240 turns x seeds {1, 42} covers both known repros in - * about 12s; trim with SIM_CRASH_TURNS / SIM_CRASH_QUICK when iterating. + * Before the SimSprite construction fix, under a frozen clock: + * - kobe trapped at every seed tried (1, 42, 1234, 99999), from 30 turns up. + * - haight trapped at seed 1 past ~240 turns. + * - deadwood — the originally reported crasher — survived all four seeds at + * 240 turns, so its reported crash needed a starting world these seeds do + * not reach. + * The cause was Micropolis::newSprite() allocating SimSprite with malloc and + * then assigning to its std::string member, which had never been constructed. + * Defaults (240 turns x seeds {1, 42}) reproduce the two cities that used to + * fail, so they stay as the regression floor; trim with SIM_CRASH_TURNS / + * SIM_CRASH_QUICK when iterating. */ import { describe, expect, it } from 'vitest'; @@ -96,12 +98,10 @@ const BUILTIN_CITIES = [ 'yokohama' ] as const; -// Cities with a known or historically reported out-of-bounds trap. Under a -// frozen clock these are reproducible rather than flaky, so they stay in the -// sweep as regression coverage rather than being skipped. haight is in the list -// too: it traps at seed 1 past ~240 turns, which is how we learned this is not -// a kobe-specific bug. -const KNOWN_CRASHERS = ['deadwood', 'haight', 'kobe'] as const; +// Cities that used to trap out of bounds (kobe, haight) or were reported to +// (deadwood). Fixed now, but labelled in the test output so a regression here +// is immediately recognizable as the old bug returning. +const FORMER_CRASHERS = ['deadwood', 'haight', 'kobe'] as const; // One turn is one cityTime increment = 16 ticks (simulate.cpp's 16-phase // phaseCycle). @@ -166,8 +166,8 @@ async function runCity(city: string, seed: number, runTurns: number): Promise { for (const city of cities) { for (const seed of seeds) { - const known = (KNOWN_CRASHERS as readonly string[]).includes(city); - const label = `${city} survives ${turns} turns (seed ${seed})${known ? ' [known crasher]' : ''}`; + const known = (FORMER_CRASHERS as readonly string[]).includes(city); + const label = `${city} survives ${turns} turns (seed ${seed})${known ? ' [former crasher]' : ''}`; it(label, async () => { await expect(runCity(city, seed, turns)).resolves.toBeUndefined(); diff --git a/apps/micropolis/src/lib/micropolisengine.js b/apps/micropolis/src/lib/micropolisengine.js index c2cc08c8..58889d86 100644 --- a/apps/micropolis/src/lib/micropolisengine.js +++ b/apps/micropolis/src/lib/micropolisengine.js @@ -92,7 +92,7 @@ if (ENVIRONMENT_IS_NODE) { // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) -// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpix8say0m.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpqiu_dt4s.js if (!Module['expectedDataFileDownloads']) Module['expectedDataFileDownloads'] = 0; Module['expectedDataFileDownloads']++; @@ -220,21 +220,21 @@ Module['FS_createPath']("/", "cities", true, true); })(); -// end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpix8say0m.js -// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpe45adt7b.js +// end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpqiu_dt4s.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpo2zrqfi8.js // All the pre-js content up to here must remain later on, we need to run // it. if ((typeof ENVIRONMENT_IS_WASM_WORKER != 'undefined' && ENVIRONMENT_IS_WASM_WORKER) || (typeof ENVIRONMENT_IS_PTHREAD != 'undefined' && ENVIRONMENT_IS_PTHREAD) || (typeof ENVIRONMENT_IS_AUDIO_WORKLET != 'undefined' && ENVIRONMENT_IS_AUDIO_WORKLET)) Module['preRun'] = []; var necessaryPreJSTasks = Module['preRun'].slice(); - // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpe45adt7b.js -// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpzebvsldf.js + // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpo2zrqfi8.js +// include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmp9t7wk17a.js if (!Module['preRun']) throw 'Module.preRun should exist because file support used it; did a pre-js delete it?'; necessaryPreJSTasks.forEach((task) => { if (Module['preRun'].indexOf(task) < 0) throw 'All preRun tasks that exist before user pre-js code should remain after; did you replace Module or modify Module.preRun?'; }); - // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmpzebvsldf.js + // end include: /var/folders/mk/wy170mjx2p5dkf12nlrqcsmc0000gn/T/tmp9t7wk17a.js var programArgs = []; diff --git a/apps/micropolis/src/lib/micropolisengine.wasm b/apps/micropolis/src/lib/micropolisengine.wasm index 1082bf892d73f0ba2c5a8466344b73845415ef99..8c9ae38013715819ebdca153fe7c5946afe8c269 100755 GIT binary patch delta 12930 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 yJieBWRE-oBlknVp@Pot@ctzh4P`d^vPY0hRx< zU(J=&cYJai`_E&6bL~p^{@uEn=GoQm-TQntpr`3n)~k|X@=|-qRLxeYOXmUIOiS$E z0|xf**1!KJ{Y@L}wr(E}=-R*6rvtn7O&u_>bJyOfz54bVXj&`?RKe`KtS`0j@CiXd zssF(3+^5ecT}>0S)@mimq^u)awBPK&Re}2g_Xo}(GfBhaS+XNrTWZU(t+K7LEwL@L zeQ(RP<=NKR*4uR3T3g(@fJL_DSsNXt$+WEVju5)Q_N{GU)+@)I^c(iO0rvtP20RLQ z9Pl*YS-|svmw^R=L#@NDBdw#YW3A(@6Rnf2ql3=b&f6~7{;&s;B=u#oU;pj{@LK3o#ReL7YCOD_Q2_#!C5^ ze2Ft4RYp!=aV-D$IL20foCAElq?i$NUCxp#jPDXlTi#mvNaZk}_ z=8>kJPTYFp@N{PCMLN<(QUXVG$0pL#gK=atiF9|M=2o&61-rSGG1+M)e$t&IPdQDJ^{;;>vv~(cQuQwmk+(e~4-S)-u92_xokz(>o{_%CNexfu)-R;J zt5a7W{~H;?jVrkIIz`Ihh>r8*deFz6H5dtu_s)~io{_MNq?(6O_8&y{FnW5~VC1-3 zgi+{UMHrpEPEtLLhTrrsO4iA3a$a&vLtYp#TvXTimh>b}@h?>ubLSc2Z)B>&tNUc%NINcDwRBl(G zHt8BV!EKwA;{xdy>{2n}YY=Lx)0)Qe;YpCR(mj4XSW5FqIy+Rdygo}o7@x(^-K=n_ zl82SI)52LDk0@ldyM$zMiBea$JEZsAa_P&XP#`y(Ii;kDp3Wy_q*c6w%V@0+C?_rU zjC@!@sxFf?qi4iO`ivO){x~=Nk&Uw0>0-q-H_O{_#m_d&%iQDFw#aUE*9CN`D1G!c zxu%D&_jXzMF}D+Zn=h|N^7nVh^WEgNe>BF$M)_f<+`>cI_9r>b<;fGNa3RAP@E++UMF(l1tN)hM7-{5^*@O6KK z_jSS7{|){r+n6CZJLP<~RB!fB?nul_4J0RF%k>S9gk;4%50&RT+n!S*BkOL6-Qcfk?V8BaxE}{gy zLZ1*#33zv@6bC*9@-1m}He6^%tdP`S^2_=#)qh=ir5z!Q^x;JL-b!Xbf?cWSvx=Wh zn!*<9!|Y0$7rCR~j8N(mpS40_iOte$lvK9!gW$QQG{ZRG*r9xGxgn?|u`%?64#mf_ zStO^zHwz*sL%Ar$OxEaCqm+|oav4~xGG3Xh5P#_WLW-uEgbUUW#VWHUf0yMY=YbUx zlIvf_E3YK-LLXFB>E%V%!^K)kHL?!E5|t8R-y4!S3AdJEMak&oLaRjOQ~L@R0@uGA zmo0~riArO#2+AZWlgKPspQJP;zw2sk<&bQBpoTj0$BxJ_MGfV)OLO=+AL!TWD)0GP z4}1M>sIK!cG-{^QBXgly61BmiW=eZpTtPEs1bG1?nk#is?p@8f&gSW(S}Hk3R1?nE zR=YLo(H*9!Gnm8Xv{7nflA)jeK*{t%u|EDt`N<3I`$-=q658}pg2@m3y$1OSmiJK- zF@3n&M>!QqHp0%yN(Zt**D{qM{$zqaZ=UjtLMLbea157xkNVV z({hwle==Qvuo;$_E{E*HNy6$zZC2B^|xUlVScOugN91Fo=EZhS!J$wNusXa2T=Pzl zn5G)<2xt7w!ftaOnp#%ca8-8r7^6N35`x5g0*8w?M=^~$H7=5wQxx+ze`_IukUx~x z)F1t%RP)A-5qn4pCAal=4=FX3fTu3$c<3oBDeS49eGL7sxPWtzl|L-K*;9CMT-ivT z!Kz=BE9oyenZ*kgpb=aSdnNj+r(YoY)YH!p%2Yi4p`tH)`olzD^7My`K4!r#{v&wb ztPwF=SqZGaDRwdvD*UFDC3p2!zbQ8fd7#%lp%97OgUnOthTTso<&5`l$P?yEgVDq_`|{;>a))(c+Nb5Tjy}bhdMp45Mq7K zv0ct59D=b?5PCtWOGZJ*3(7F^SU-OO_nCX!NY1i+rj$RFL4_d3K?nX&h9cYd{!~6E zYlt4?j1$g`uibA1)pDXaj6}*23XCJMPy`!YPVT~rl@VcigK)IIP zQ(BYJP;gJVT{wwY&-44rLNW&CJy0UB*=jV{3+Hah1jO~KM?6$wRr^@Jx&$fObuel% z&Oc0SEOdFU404ZEG{(weK_tfRJXdOwaZu@nQj^?;UN4mKWSsu+g<^Sw(A+S#SGcTE zko-zXCgWlJD`h%H9!G(4qKJlg_N99fT8WT}P*tLl)(OJp%u*ex4xY?%3@Oeg>AfZD zCzG@KU`j*zAv0a2Uy-qTfEVq`9q%(AnndnHo(}~w1sWHlpJB7382!TNg+NO){Q%$B znCX{@SKdM!kb8PR3k@VB3r70l^jVPWOS|J>s2{Sv2ibl!0ej8;X&{{E@An|cpH??O zb+9+ppQd5&r9aIh6X1JB>uWQ)=%Jd-bIMt+cOC#SxS%7k*CoL=w@`|Hw*2Decw6@Pm&H?9T&@qHol-PIBE0EUqUc$j_X_k=(>qA;XSFm=2rlG|_7VA7mTh6?URr9fQ;< z6o5G&%i{p9e|&`xgJ?b9)rN4yStX6FfmK1Yl;0w8{&SV0z-!?`5Vg1Y-i5|PU&REe zny`ZlC6|vSNE#p0913bQIt`N}H;J=angFppj^{ELK`2`XO@h&b*Fn!0sA&IR+`4IC6|1YwpSRq;rIn%-!ja}m5bI%ZXFbX=c+vP~SvBL%K{GY4Qa zj|ib<*cO9>%X+BcRw1;8wAE#~nE9*TalX9<^3b)&UU(7>t#3^Zx_6D&@lMvI-(v7qdu6dG!Lf{@Gy*aAba?G z6S5oTh10r!b4)?K*PuqZ%EI6X8b^MF%#z5iPdBOf^dv=tClRy@Im+q7;PVnR+VB7e z-)|9!8_R_6i5u3U^A-_R`L2#!gHviCp(_uKigyM<}Ef&@iZ^(f?uMy+WfM zl3m_}#}_vMZgwj>U}%(|Y1p^$%)%IEKN}<75)W=XhcP_vAT)BIN)N$A2W=O8*kuq- zY;vIMbGc=9gfHAlju{F+;klGnQi=V>#t_`t0b^5m za|yYJ{S3{bsH4O&W9D$4ec|K^pC9vYJgza-I|!4b=wi0zb&VV~w4WMHlgTfzIGQ#p z_N&3g!hVBi(X_PXciu-Xcut*wilq?oPlIS;DU8#9fLxkZ^}EE`vaRBId72T3+LxxM z-4_FTqcU^~!4u$2SsGFd+sE<4l=z+x zeu*@a+=R-B)Ee;`CLXsQOnlFWE(YevMA|pRRlq1bozLUxt04(uS(0cy+zD7KBulq( z^CeL$tVp6xXqtq?0Y^i1A^zA;~QLU7@8umE%wEY4u$mXl21x4wyk9@2o?_^m^13Vk2r*Vq*5 zENN7*ict0&w4z^2p{WWcknc36y|KJo*O;~_J)e7Vf}|MxUttT3zAKj$2b+m~3zeGC zA8@vtO=z3qh4N@z=3vzKom8}Y@Av3h#EEG7A2_p{3Y;d*{sU)!Gnz?G!`Y5B2z?K= z@I~|gz;4o!GN3JJTU=4c7I2?g%7NX zomd{>-oMkH#`|s@=_!`kDTwVrE5q@Y^u52aaJ0rupnfYFKm=K37~P8c5*PkYt=@!R z+Ku{U>q68Gt>1)dgR*T3S@&)u@V{(BzxtaLxDXfDcxd02THFH6X!~Za-t7vxHfmQW z#kh8F#(!1>Kk@xS{IB1CGyaYD3;D;kFU0TL{>}Jj+q?0T4AIm7wm_lg!yVjMmHiHH ze$70N59n0r(42;$y$^lx-~N$_(FVP#dc-;I40xgD+HK*lZ?-BQ{wtf(u;W9T;eF3V z501_l!p;|Q(J`t2O8W|Dzuk$pfH1Rg-#^TVSg74d(XkA zt7uY;W+XP@&kZ3g7G@T=fmpZ1=W9v~iDP%*Y*!jXo`Ua3v)k|i*u2Ms67VO0;z&4A}M@i4!* z6am$G(h6h_eAJUx#T%(vJ?VR1-|(=c$xx@H5(QU2L8RDTw7S|=Vm*cih*R?jHb8GK$8o3Za)=t#MQzdBR{ zaVcb{(P|R75`jO{=-VV4!h6#RSePv9O~00wPlJA~@OwdxKD3sy0%;+?H7NOYt}&r8^aNogW4clV-x&`dD8&(7Kib@TOAaP9k-~DBIx^ zdJvO+VTKPrrR8lppFvb3Xi8T{f%MPmTYRz3j?&u_+X6oQXyedL92ILQlm#mceqV|e z#%2!8njc>SwlkT+Dnulk;dwtAUfRf=ws8dUiNsh1VLuoU-bH#y>`$vp>s*S(nkyWZ z_NTF4+Xa>c3wqKNuY7~V@QkJd&?$DfAcanGXu!WYh3`Px50m`#fwXnSpE#YN6{Bjw z)v(I zgVT?3{7`XUi26Bh%vTX#f8np}SN`Ocu(M2Bf>f4krYw4yjJG2EloG^PUn960PnOC` zRg5GQ(?DLv8;D+*4gStiMcyvT`xCs2ZUcXQf#vr}un(dYG3wSEL~CL%eGod@N!UJ! zJ|J5R^I!RrM&k)=8B7~^o#DjnEWAIMwjk#qXE1Guk?-MPx*D6sUtx}R0Y-jJ-ys+E zLtkT2M6N)EA(%Ihg?>XY3%&|+I_~Cc5Svb`N_Sma8k|nsmJr&*+esA*Qg@cGx%)rE z?R46l`~}Vo8q(sr5aI@Z3It3LZ}Lup6pZAghA9Xe1m5DnP&2;;4R%-%hK`6gqXMcS zDetS!pfrq>IT^GmhR7G3{e6yrn&!8d{PI=vGQO)n@DO56@zB*_kNAtg<&R+g5Er0~ zJ$9o!fgVHggt!IchSEL7MOlxLkH--a0iVK0!|4C$GmZ`$KgYzm%y4=@dTwle-Dd=b zN9o11?9NgQbRJ1R#Y*$&NLoeP!wFn2Y20dys?D1j1ACzKC|Vzpdyk?a*i0FX_dzp8 z(WqF%i$#PkO2Oka-?MBazf<5BsiNm$5HN;D_{nBZn81l3b~H_SZHfe|n>)W|_pmrP z3QxPDoPzK!w%iyx5U>2c8H3U6C11?HM6*|}Xcjz{){oBM!igJjG`8XKg*q2@U_&8e zEPY!laJkpdV`=Mu4RTeX_c$6W<+>{9)#LDJP9KX&WZ-z(jtqxB<7p!@0yd0C|F{a? z6X-C!#T-9@Mq#19dIBD=d*JK@%)Ukgok;t?X)M%NoOpOG!gV_u?oFgqyj&p<#!W)U z_yJZ*)(2ua{~B=cA&F4D%}2th5(y~!DSwFokwev`TD|nw3tj5!>VuTB>dKv zxRCl2Y1VYed`pRiH60|X4_id{60%fp{2jf(+nA+v0Ey4x3@!M9Kz3#xG_eW%0v_K9 zGX})(WyX~8z$KOg$CjZUR_b3br)FaPj?-5%nQEA1T#{7LBx71Odj$PDo_csWkq_rqQ2tFCKVO!Byc{~1%+X_4(y9DA)sEHFO2{VoZ4J8C zW+<~31Me2-i4F8vOMPM8S}b2)=!e(RZc^Ab&a@`6;^yNd?XW!$cY8jZ%)^Xq2Uyq9Ie|ZVG{K2@jpjnz31`+}!sjN^tFEVvkX?EMoyPL^ z^9?ja!qPr-6Ix;#9Nr{mDA>MxVb|G0 zb8P##Ag{^q$Myr+D!4nh;yuU#&bAc!85V8D-E@%4R|Ohvqe0|=-gO%~0y`$ui)k6I z!#Ri@g8$`H2RWqA%g2q1?e!frQXIn)fexkwYk+U?L{51UkX`y3n88!rV-X9GTX%mFgkJZ#E9N%KVAwJB?xxn zEc)65w3@m*|=l_QHO1mKA zciaWL;KA>Bg>(wqoS@~rPVnjQyWFS~G}-!0%#D|+o!C6#&?Yty&Yhs~v8VWmgJ50c z#Nj9FvDCs~?s;coTqFGj3bLznl4>|b=abZqrNp3b2QlREQg2UP9@F?I7QHTsuC=jN?f3cA#bGB2s;AeJm5N? z*6fezv_^6;Grj?>F4L)wTLN5xkRd3MiQVSiNKzLUl;PcyuL8WT(9!rUYVsAj207Qc ziaYKee0r63L>(NwN~=UY<}}VVTTuIEKa(?uzpy8QZxzhsk?T_^bqzPmGiY=ToiZB+ zU!&o+mjc7vwjLS#Rw*Fw{KuRH(qaa6R*|rgqd}V_Vl^SC-cQn3hB2nT20bR-=-Oa zOxBm&!QF>NV}*NY)J*WbPjko=ebaq9i{N?F`XQ<{3#L7!Louw_AJI|xac=G-bkylk z@CehzIgtDqgW7Z$^_a$m&k^GJX)G<0X}*nwk-T{F=Eo+((Z`sbPKU>j>3FP8hd-gY zEvIvQTttW#z$P2-&N$6iV_I=^L}dVTAs{di-NoeYYks(HH-Y9R*lt1s82gk)pv{*& z#d`Oie(EWGCI@+34mF3FIA(#)3<*WL@e-4r$>9A8OXP_V_X;ENOh|i$3}(WZS9GAS zTR^i`4AFfGXuQ2}T1m5DDVbqR+q?ji^YQwS0$NW+Rqdea@W|JwJkr#p^&NGYK%uu!J5Ao+#8{Dz17elArF!{HFO;zgdzJuZ#5aeFIs)nx@4N()<=yb z-QBW>X~7;%$03=oM|PL(yquXLTX*XvPKbnwc%ijIHJgVApIY!Rrao6pZ4j}7v#Kte z%oLqrt2qQ~AkAd>IPSl?X0-ttbgWtZ1)Efh8b@a9nHII41%E8IS8ht=Sr#$ z@{LY3HI$N}dUz>ymlqid7b~hwasK#9YFWJ9=uioz`4%e2s2^gJ8G{C!3u|K3dYA`2 zh*7gR#h3&&DRiMw6Il`|HO1|KpF?@5#Er;K>*o{H9ui(QG_0bzT*Il0^A#f+mMxfVTPJ=fK{!Y~fx02LP5iPa0 zx;%J~iT{K+!yPr zl`xxO_0$hA)%&=fTE@#@wF_p~QxlQZiF#@(Hr4B^tpkr4)8fKy;)9%p9ft+=)ynvN z>R5f0{yeD3Y6AHc5|dTU_M5;~-bJ&co8b|RpW1$h{>kdO=o5U%pI>Ye=e&t(BP_*u zOh0LW#rrAvtbrO8f4T_hOcAPrtm~&`H{IDHNatW*1GOtT0Wm2k*a_&AqL#ShP6YR{fd}^6(f(i}M*ndE$hFFdlOowp|)#R9sMHv6d-_0Wkk<*3T z2TWyc9{=j)WfhYe%B?jE0Fq*S_i*t%zsCX_sizv zJS#vcu=YCtj%xF{C}4a~zIsQk-0nUAZ1Jao#@t+eg0l*Y+4QD8L{J%h! ze-bhmh#MKN6vWMH1M^!Zt{~{$NG*dO`?4CT?J-ff)kxhyuIWn}t2P;vqpeNxV0aD} zn&L6>45~N7fbkgGHB;XuuV7&_+%?bOR5NuFHfhaONA+i1UwMrw@{nM8jLYE za}E+71%IO#^9nEE#u*CDTB6TAgRfeu4^iNLtuS^!h6AnD(y*tMdfWg-^MedaZoFq< wVlcz{nDY_7gnNbmYD;0mpi>*Q`kO~qwo#)jqxs@OaY+jPaQk!{^~DGO2MYxJ^#A|> diff --git a/packages/micropolis-engine/src/micropolis.cpp b/packages/micropolis-engine/src/micropolis.cpp index 5230c74e..ad493d5e 100644 --- a/packages/micropolis-engine/src/micropolis.cpp +++ b/packages/micropolis-engine/src/micropolis.cpp @@ -86,6 +86,13 @@ * Simulator constructor. */ Micropolis::Micropolis() : + // Must be initialized here: setCallback() does + // `if (callback != NULL) delete callback`, so a Micropolis allocated + // over previously used memory would delete a stale pointer and trap + // ("RuntimeError: table index is out of bounds"). init() does not set + // it either, so the constructor is the only place this can happen. + // Listed first to match its declaration order in micropolis.h. + callback(NULL), populationDensityMap(0), trafficDensityMap(0), pollutionDensityMap(0), diff --git a/packages/micropolis-engine/src/sprite.cpp b/packages/micropolis-engine/src/sprite.cpp index 5cab2c8f..98d18422 100644 --- a/packages/micropolis-engine/src/sprite.cpp +++ b/packages/micropolis-engine/src/sprite.cpp @@ -111,7 +111,17 @@ SimSprite *Micropolis::newSprite(const std::string &name, int type, int x, int y sprite = freeSprites; freeSprites = sprite->next; } else { - sprite = (SimSprite *)newPtr(sizeof (SimSprite)); + // Must be `new`, not newPtr()/malloc(): SimSprite has a std::string + // member, and assigning to an unconstructed std::string dereferences + // whatever garbage its internal pointers hold. That is a segfault in + // `sprite->name = name` below on any heap that is not zero-filled -- + // reached via doSpecialZone -> doAirport -> generateCopter -> + // makeSprite. Sprites are pooled on freeSprites and never freed (see + // destroySprite) -- a pre-existing leak on every destroy()/init() that + // this widens slightly, since each sprite's std::string can itself + // heap-allocate. Fixing that means actually deleting pooled sprites + // in destroySprite, which is a separate change from this crash fix. + sprite = new SimSprite(); } sprite->name = name;