From 4fcc140bafeadb0521dce4dd639006bfb880fe83 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 29 Aug 2026 00:22:53 +0200 Subject: [PATCH] fix: restore MVP Wasm compatibility The generated binary contained a DataCount section that Node.js 10 rejects before parser initialization. MVP-only compiler and optimizer settings prevent the unsupported section. --- .github/workflows/build.yml | 91 +++++++++++++------------ .github/workflows/node.js.yml | 30 -------- Makefile | 2 +- build/Makefile | 4 +- build/test-legacy-browser.mjs | 125 ++++++++++++++++++++++++++++++++++ lib/lexer.wasm | Bin 22167 -> 22164 bytes package.json | 1 + test/compatibility.js | 11 +++ test/compatibility.mjs | 11 +++ test/legacy.html | 14 ++++ 10 files changed, 212 insertions(+), 77 deletions(-) delete mode 100644 .github/workflows/node.js.yml create mode 100644 build/test-legacy-browser.mjs create mode 100644 test/compatibility.js create mode 100644 test/compatibility.mjs create mode 100644 test/legacy.html diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9a7a39..8da5168 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,63 +6,66 @@ on: pull_request: branches: main -env: - WABT_VERSION: "1.0.24" - EMCC_VERSION: "1.40.1-fastcomp" +permissions: + contents: read jobs: build: runs-on: ubuntu-22.04 + strategy: + matrix: + node: [18, latest] + steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - - name: Prepare - id: preparation - run: | - export PWD=$(pwd); - echo "::set-output name=PROJ_ROOT::$PWD"; + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} - name: Set up Docker uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Install - run: npm install + run: npm ci - - name: Install wabt - env: - PROJ_ROOT: ${{ steps.preparation.outputs.PROJ_ROOT }} + - name: Compile to Wasm and test run: | - cd $PROJ_ROOT; - cd ../; - if [[ "$RUNNER_OS" == "Linux" ]]; then - export WABT_OS="ubuntu"; - fi - if [[ "$RUNNER_OS" == "macOS" ]]; then - export WABT_OS="macos"; - fi - if [[ "$RUNNER_OS" == "Windows" ]]; then - export WABT_OS="windows"; - fi - curl -sL https://github.com/WebAssembly/wabt/releases/download/${WABT_VERSION}/wabt-${WABT_VERSION}-${WABT_OS}.tar.gz -O - # check if package downloaded - ls -la - tar xvf wabt-${WABT_VERSION}-${WABT_OS}.tar.gz - # check if wabt binaries installed - ls -la ./wabt-${WABT_VERSION}/bin/ - - name: Compile to Wasm & Test Wasm - env: - PROJ_ROOT: ${{ steps.preparation.outputs.PROJ_ROOT }} + rm lib/lexer.wasm + npm run build-wasm + git diff --exit-code -- lib/lexer.wasm + npm run build + npm test + + - name: Legacy browser test (Firefox 67) + if: matrix.node == 18 run: | - cd $PROJ_ROOT; - rm lib/lexer.wasm; - npm run build-wasm; - npm run build; - # test - npm run test; + sudo apt-get install -y libdbus-glib-1-2 libxt6 >/dev/null + curl --fail --location --silent --show-error \ + https://archive.mozilla.org/pub/firefox/releases/67.0.4/linux-x86_64/en-US/firefox-67.0.4.tar.bz2 \ + --output firefox-67.0.4.tar.bz2 + echo "45182265a7fdc3059088502e9f1a9ec6be54a99138d5215f3a46b7e33384e1bdd8d21a5ef5fe5efb99ae3873f146fda9a26c028b114b46bf04c5c3e6812f4913 firefox-67.0.4.tar.bz2" | sha512sum --check + tar xjf firefox-67.0.4.tar.bz2 + curl --fail --location --silent --show-error \ + https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz \ + --output geckodriver-v0.26.0-linux64.tar.gz + echo "d59ca434d8e41ec1e30dd7707b0c95171dd6d16056fb6db9c978449ad8b93cc0 geckodriver-v0.26.0-linux64.tar.gz" | sha256sum --check + tar xzf geckodriver-v0.26.0-linux64.tar.gz + FIREFOX_BIN=$PWD/firefox/firefox GECKODRIVER=$PWD/geckodriver node build/test-legacy-browser.mjs + - name: Benchmark Wasm - env: - PROJ_ROOT: ${{ steps.preparation.outputs.PROJ_ROOT }} - run: | - cd $PROJ_ROOT; - npm run bench; + run: npm run bench + + - name: Use Node.js 10 + if: matrix.node == 18 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 10 + + - name: Test Node.js 10 compatibility + if: matrix.node == 18 + run: npm run test-compatibility diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index c993415..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: Node.js CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm run build --if-present - - run: npm test diff --git a/Makefile b/Makefile index bf45b62..6be2ca9 100755 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ lib/: @mkdir -p $@ optimize: lib/lexer.wasm - $(WASM_OPT) -Oz --enable-bulk-memory --strip-debug lib/lexer.wasm -o lib/lexer.wasm + $(WASM_OPT) -Oz --mvp-features --strip-debug lib/lexer.wasm -o lib/lexer.wasm clean: $(RM) lib/* diff --git a/build/Makefile b/build/Makefile index b8e96d8..3d704b9 100755 --- a/build/Makefile +++ b/build/Makefile @@ -5,7 +5,7 @@ WASM_LDFLAGS := -nostartfiles # These are project-specific and are expected to be kept intact WASM_TARGET := -target wasm32-unknown-wasi -WASM_EXTRA_CFLAGS := -I include-wasm/ -Wno-logical-op-parentheses -Wno-parentheses -Oz +WASM_EXTRA_CFLAGS := -I include-wasm/ -Wno-logical-op-parentheses -Wno-parentheses -mno-bulk-memory -Oz WASM_EXTRA_LDFLAGS := -Wl,-z,stack-size=13312,--no-entry,--compress-relocations,--strip-all WASM_EXTRA_LDFLAGS += -Wl,--export=__heap_base,--export=parseCJS,--export=sa WASM_EXTRA_LDFLAGS += -Wl,--export=e,--export=re,--export=es,--export=ee @@ -18,7 +18,7 @@ lib/lexer.wasm: include-wasm/cjs-module-lexer.h src/lexer.c $(WASM_LDFLAGS) $(WASM_EXTRA_LDFLAGS) optimize: lib/lexer.wasm - ${WASM_OPT} -Oz lib/lexer.wasm -o lib/lexer.wasm + ${WASM_OPT} -Oz --mvp-features lib/lexer.wasm -o lib/lexer.wasm clean: rm lib/* diff --git a/build/test-legacy-browser.mjs b/build/test-legacy-browser.mjs new file mode 100644 index 0000000..3569bb5 --- /dev/null +++ b/build/test-legacy-browser.mjs @@ -0,0 +1,125 @@ +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import { readFile } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import { setTimeout } from 'node:timers/promises'; + +const files = new Map([ + ['/dist/lexer.mjs', ['dist/lexer.mjs', 'application/javascript']], + ['/test/legacy.html', ['test/legacy.html', 'text/html']] +]); + +/** + * @param {import('node:http').IncomingMessage} request + * @param {import('node:http').ServerResponse} response + */ +async function serve (request, response) { + const file = files.get(request.url.split('?')[0]); + if (file === undefined) { + response.writeHead(404); + response.end(); + return; + } + + try { + response.writeHead(200, { 'content-type': file[1] }); + response.end(await readFile(file[0])); + } + catch { + response.writeHead(500); + response.end(); + } +} + +const server = createServer(serve); +await new Promise(resolve => server.listen(8123, resolve)); + +const driver = spawn(process.env.GECKODRIVER, ['--port', '4444'], { + stdio: 'inherit', + env: { ...process.env, MOZ_DISABLE_CONTENT_SANDBOX: '1', MOZ_FORCE_DISABLE_E10S: '1' } +}); + +/** + * @param {string} method + * @param {string} path + * @param {object} [body] + * @returns {Promise} + */ +async function drive (method, path, body) { + const options = { method, signal: AbortSignal.timeout(10000) }; + if (body !== undefined) { + options.headers = { 'content-type': 'application/json' }; + options.body = JSON.stringify(body); + } + const response = await fetch('http://127.0.0.1:4444' + path, options); + const json = await response.json(); + if (!response.ok) + throw new Error(`${path}: ${JSON.stringify(json).slice(0, 400)}`); + return json.value; +} + +async function waitForDriver () { + for (let attempt = 0; attempt < 100; attempt++) { + try { + await drive('GET', '/status'); + return; + } + catch (error) { + if (attempt === 99) + throw error; + } + await setTimeout(100); + } +} + +let failure; +let sessionId; +try { + await waitForDriver(); + const session = await drive('POST', '/session', { + capabilities: { + alwaysMatch: { + 'moz:firefoxOptions': { + binary: process.env.FIREFOX_BIN, + args: ['-headless'] + } + } + } + }); + sessionId = session.sessionId; + await drive('POST', `/session/${sessionId}/url`, { url: 'http://127.0.0.1:8123/test/legacy.html' }); + let title = 'RUNNING'; + for (let attempt = 0; attempt < 30 && title === 'RUNNING'; attempt++) { + await setTimeout(500); + title = await drive('GET', `/session/${sessionId}/title`); + } + if (title !== 'PASS') + failure = title === 'RUNNING' ? 'timed out' : title; +} +catch (error) { + failure = error.message; +} +finally { + if (sessionId !== undefined) { + try { + await drive('DELETE', `/session/${sessionId}`); + } + catch (error) { + failure = failure === undefined ? error.message : `${failure}; cleanup: ${error.message}`; + } + } + if (driver.exitCode === null && driver.signalCode === null) { + const exit = once(driver, 'exit'); + driver.kill(); + await exit; + } + await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); +} + +if (failure) { + console.error(`legacy browser test: ${failure}`); + process.exitCode = 1; +} +else { + console.log('legacy browser test: PASS'); +} diff --git a/lib/lexer.wasm b/lib/lexer.wasm index 3ff889cb0472640070395de659f0584fbac54dff..d58cc87e730790acf37dc69dc3bc2efc02e82f4a 100755 GIT binary patch delta 14 VcmbQfmT}5j#tqVpo8=fQLjft!1oQv^ delta 18 ZcmbQTmT~%8#tqVp%sh-to23~mLjgCL1p@#8 diff --git a/package.json b/package.json index c1edff5..ab04f26 100755 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test-wasm": "cross-env WASM=1 NODE_OPTIONS=--disallow-code-generation-from-strings mocha -b -u tdd test/*.js", "test-wasm-sync": "cross-env WASM_SYNC=1 NODE_OPTIONS=--disallow-code-generation-from-strings mocha -b -u tdd test/*.js", "test": "npm run test-wasm && npm run test-wasm-sync && npm run test-js", + "test-compatibility": "node test/compatibility.js && node --experimental-modules test/compatibility.mjs", "bench": "node --expose-gc bench/index.mjs", "build": "node build.js ; babel dist/lexer.mjs -o dist/lexer.js ; terser dist/lexer.js -o dist/lexer.js", "build-wasm": "make lib/lexer.wasm ; node build.js", diff --git a/test/compatibility.js b/test/compatibility.js new file mode 100644 index 0000000..b19ca0b --- /dev/null +++ b/test/compatibility.js @@ -0,0 +1,11 @@ +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); + +const result = require('../lexer.js').parse('exports.value = 1'); +const wasm = new WebAssembly.Module(fs.readFileSync('lib/lexer.wasm')); + +assert.deepStrictEqual(result.exports, ['value']); +assert.deepStrictEqual(result.reexports, []); +assert.strictEqual(typeof new WebAssembly.Instance(wasm).exports.parseCJS, 'function'); diff --git a/test/compatibility.mjs b/test/compatibility.mjs new file mode 100644 index 0000000..65c45ac --- /dev/null +++ b/test/compatibility.mjs @@ -0,0 +1,11 @@ +import { init, parse } from '../dist/lexer.mjs'; + +init().then(function () { + const { exports, reexports } = parse('exports.value = 1'); + + if (exports.length !== 1 || exports[0] !== 'value' || reexports.length !== 0) + throw new Error('Unexpected parse result'); +}).catch(function (error) { + console.error(error); + process.exitCode = 1; +}); diff --git a/test/legacy.html b/test/legacy.html new file mode 100644 index 0000000..848a3c2 --- /dev/null +++ b/test/legacy.html @@ -0,0 +1,14 @@ + +RUNNING +