diff --git a/.eslintrc.js b/.eslintrc.js index e08d79be..ea9f7658 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,7 +9,7 @@ module.exports = { extends: ["prettier"], parserOptions: { - ecmaVersion: 9, + ecmaVersion: 2020, }, globals: { diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index bb422d05..e02bfdb5 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -38,4 +38,43 @@ jobs: toolchain: ${{ matrix.toolchain }} override: true default: true + # `override: true` sets a directory override on the repo root, but the + # nearer wasm-mappings/rust-toolchain.toml takes precedence over it, so + # select the matrix toolchain explicitly. - run: JOB=${{ matrix.job }} ./ci/script.sh + env: + RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }} + + dist: + name: WASM binary is reproducible + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # No toolchain override here on purpose: the build must use the version + # pinned in wasm-mappings/rust-toolchain.toml for the byte comparison + # to be meaningful. + # wasm-pack vendors its own wasm-bindgen; wasm-opt must be the binaryen + # version build.sh pins, since wasm-pack's fallback download is older + # and produces different bytes. + - name: Install wasm-pack + env: + WASM_PACK_VERSION: v0.15.0 + WASM_PACK_SHA256: c09f971ecaed9a2efc80fdcea7a00ef6b53c7fadc8c57d1f61b53a6aa66b668a + run: | + name="wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl" + curl -sSfL -o "$RUNNER_TEMP/wasm-pack.tar.gz" \ + "https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/${name}.tar.gz" + echo "${WASM_PACK_SHA256} $RUNNER_TEMP/wasm-pack.tar.gz" | sha256sum -c - + tar -xzf "$RUNNER_TEMP/wasm-pack.tar.gz" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/${name}" >> "$GITHUB_PATH" + - name: Install binaryen + env: + BINARYEN_VERSION: "132" + BINARYEN_SHA256: 195ddc94f9bc89f45abdabb0b9eea86023d727ba90eac8b35b80f2544fc30572 + run: | + curl -sSfL -o "$RUNNER_TEMP/binaryen.tar.gz" \ + "https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/binaryen-version_${BINARYEN_VERSION}-x86_64-linux.tar.gz" + echo "${BINARYEN_SHA256} $RUNNER_TEMP/binaryen.tar.gz" | sha256sum -c - + tar -xzf "$RUNNER_TEMP/binaryen.tar.gz" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/binaryen-version_${BINARYEN_VERSION}/bin" >> "$GITHUB_PATH" + - run: JOB=wasm-dist ./ci/script.sh diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index bcb3d7b0..7c5eece8 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [12, 14, 16, 18] + node-version: [18, 20, 22] steps: - uses: actions/checkout@v3 diff --git a/.prettierignore b/.prettierignore index 9179b1bd..20576196 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,14 @@ bench/ +test/source-map-tests/ coverage/ node_modules/ target/ + +# Generated by wasm-mappings/build.sh; reformatting would break the +# byte-for-byte check in the `wasm-dist` CI job. +lib/mappings-glue.js +lib/mappings-glue.d.ts +lib/mappings-glue-browser.mjs +lib/mappings-glue-browser.d.mts +wasm-mappings/source-map-mappings-wasm-api/pkg-node/ +wasm-mappings/source-map-mappings-wasm-api/pkg-web/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a00072f..3f048037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## Unreleased + +- Require Node.js 18 or later. `lib/mappings.wasm` is now built with + wasm-pack and uses WebAssembly multi-value and reference types, which + older Node.js versions do not support. + ## 0.8.0 - [#528] https://github.com/mozilla/source-map/pull/528 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3509743..f4dbf346 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,19 +40,17 @@ If you are filing an issue for a bug or other misbehavior, please provide: ## Building From Source -Install Node.js `12` or greater and then run +Install Node.js `18` or greater and then run $ git clone https://github.com/mozilla/source-map.git $ cd source-map/ $ npm install -Next, run - - $ npm run build - -This will create the following files: - -- `dist/source-map.js` - The plain browser build. +There is no build step for the JavaScript: the package is used directly from +`source-map.js` and `lib/`. The only generated files are `lib/mappings.wasm` and +its glue, which are committed; see +[Updating the `lib/mappings.wasm` WebAssembly Module](#updating-the-libmappingswasm-webassembly-module) +if you need to change them. ## Submitting Pull Requests @@ -65,16 +63,17 @@ force push to the remote branch to update the pull request. ## Running Tests -The test suite is written for node.js. Install node.js `12` or greater and +The test suite is written for node.js. Install node.js `18` or greater and then run the tests with `npm test`: ```shell $ npm test -> source-map@0.7.3 test /Users/fitzgen/src/source-map -> node test/run-tests.js +> source-map@0.8.0 test +> git submodule update --init --recursive; node test/run-tests.js -137 / 137 tests passed. + +190 / 190 tests passed. ``` ## Writing New Tests @@ -134,40 +133,59 @@ Ensure that you have the Rust toolchain installed: $ curl https://sh.rustup.rs -sSf | sh ``` -The `wasm32-unknown-unknown` target is nightly-only at the time of writing. Use -`rustup` to ensure you have it installed: +The exact toolchain version is pinned in `wasm-mappings/rust-toolchain.toml` +and `rustup` will install it on demand, including the +`wasm32-unknown-unknown` target. +Note: The pin exists because CI rebuilds the +committed artifacts and fails if they differ byte-for-byte, which only works +if everyone builds with the same compiler. + +Install [`wasm-pack`](https://wasm-bindgen.github.io/wasm-pack/) version +0.15.0, the version CI uses, which vendors its own `wasm-bindgen`: ``` -$ rustup toolchain install nightly -$ rustup target add wasm32-unknown-unknown --toolchain nightly +$ cargo install wasm-pack --version 0.15.0 --locked ``` -Move to wasm sources folder: +You also need `wasm-opt` from [binaryen](https://github.com/WebAssembly/binaryen) +**version 132** on your `PATH` (for example `brew install binaryen`, or a +[release download](https://github.com/WebAssembly/binaryen/releases/tag/version_132)). +`build.sh` refuses to run with any other version: without one on `PATH`, +wasm-pack falls back to downloading an older binaryen, whose output does not +match the committed `lib/mappings.wasm`. + +Make sure the crate's tests pass: ``` $ cd wasm-mappings/ +$ cargo test ``` -Make sure the crate's tests pass: +Then rebuild the shipped artifacts: ``` -$ cargo test +$ ./build.sh ``` -Ensure that you have the following wasm post-processing tools installed: +This writes five files into `lib/`: -- `wasm-nm`: https://github.com/fitzgen/wasm-nm -- `wasm-gc`: https://github.com/alexcrichton/wasm-gc -- `wasm-snip`: https://github.com/fitzgen/wasm-snip -- `wasm-opt`: https://github.com/WebAssembly/binaryen +| File | Purpose | +| ----------------------------- | -------------------------------------------- | +| `mappings.wasm` | the WebAssembly module itself | +| `mappings-glue.js` | `wasm-bindgen` glue for Node (CommonJS) | +| `mappings-glue.d.ts` | types for the above node glue file | +| `mappings-glue-browser.mjs` | `wasm-bindgen` glue for browsers (ES module) | +| `mappings-glue-browser.d.mts` | types for the above browser glue file | -(These dependencies should automatically be installed by cargo) +All five are generated and committed; do not edit them by hand. `package.json`'s +`browser` field swaps `lib/read-wasm.js` for `lib/read-wasm-browser.js`, which +is what selects between the Node and browser glue. -Build Rust crate as a `.wasm` file: +Set `PROFILING=1` to enable the crate's `profiling` feature, which wraps each +query in a `console.time` / `console.timeEnd` pair: ``` -$ cd source-map-mappings-wasm-api/ -$ ./build.py -o ../../lib/mappings.wasm +$ PROFILING=1 ./build.sh ``` -See further information in [wasm-mappings/CONTRIBUTING.md]. +See further information in [wasm-mappings/CONTRIBUTING.md](wasm-mappings/CONTRIBUTING.md). diff --git a/bench/package.json b/bench/package.json index 7305b72a..b34749e0 100644 --- a/bench/package.json +++ b/bench/package.json @@ -5,7 +5,7 @@ "build": "webpack" }, "dependencies": { - "webpack": "^4.9.1", - "webpack-cli": "^3.1" + "webpack": "^5.0.0", + "webpack-cli": "^4.0.0" } } \ No newline at end of file diff --git a/lib/mappings-glue-browser.d.mts b/lib/mappings-glue-browser.d.mts new file mode 100644 index 00000000..d3d1c750 --- /dev/null +++ b/lib/mappings-glue-browser.d.mts @@ -0,0 +1,97 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * A parsed `mappings` string, ready to be queried. + * + * JS owns this handle and must call `free()` on it when finished. + */ +export class Mappings { + private constructor(); + free(): void; + [Symbol.dispose](): void; + /** + * Find all mappings for the given original location and invoke `cb` on + * each of them. + * + * When `has_original_column` is false, `original_column` is ignored and + * every mapping with a matching source and original line is yielded. + */ + allGeneratedLocationsFor(source: number, original_line: number, has_original_column: boolean, original_column: number, cb: any): void; + /** + * Invoke `cb` on each mapping, in order of generated location. + */ + byGeneratedLocation(cb: any): void; + /** + * Invoke `cb` on each mapping that has original location information, in + * order of original location. + */ + byOriginalLocation(cb: any): void; + /** + * Compute column spans for the mappings. + */ + computeColumnSpans(): void; + /** + * Find the mapping for the given original location, if any exists, and + * invoke `cb` with it once. + */ + generatedLocationFor(source: number, original_line: number, original_column: number, bias: number, cb: any): void; + /** + * Find the mapping for the given generated location, if any exists, and + * invoke `cb` with it once. + */ + originalLocationFor(generated_line: number, generated_column: number, bias: number, cb: any): void; +} + +/** + * Parse a source map's `mappings` string. + * + * On failure, rejects with the numeric `source_map_mappings::Error` code. + * Replaces the old `allocate_mappings` / `parse_mappings` / `get_last_error` + * trio: `wasm-bindgen` copies the string into wasm memory itself, so JS no + * longer has to hand-manage a buffer. + */ +export function parseMappings(mappings: string): Mappings; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_mappings_free: (a: number, b: number) => void; + readonly mappings_allGeneratedLocationsFor: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number]; + readonly mappings_byGeneratedLocation: (a: number, b: any) => [number, number]; + readonly mappings_byOriginalLocation: (a: number, b: any) => [number, number]; + readonly mappings_computeColumnSpans: (a: number) => void; + readonly mappings_generatedLocationFor: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number]; + readonly mappings_originalLocationFor: (a: number, b: number, c: number, d: number, e: any) => [number, number]; + readonly parseMappings: (a: number, b: number) => [number, number, number]; + readonly __wbindgen_exn_store: (a: number) => void; + readonly __externref_table_alloc: () => number; + readonly __wbindgen_externrefs: WebAssembly.Table; + readonly __externref_table_dealloc: (a: number) => void; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; + +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/lib/mappings-glue-browser.mjs b/lib/mappings-glue-browser.mjs new file mode 100644 index 00000000..bd32547a --- /dev/null +++ b/lib/mappings-glue-browser.mjs @@ -0,0 +1,349 @@ +/* @ts-self-types="./mappings-glue-browser.d.mts" */ + +/** + * A parsed `mappings` string, ready to be queried. + * + * JS owns this handle and must call `free()` on it when finished. + */ +export class Mappings { + static __wrap(ptr) { + const obj = Object.create(Mappings.prototype); + obj.__wbg_ptr = ptr; + MappingsFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + MappingsFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_mappings_free(ptr, 0); + } + /** + * Find all mappings for the given original location and invoke `cb` on + * each of them. + * + * When `has_original_column` is false, `original_column` is ignored and + * every mapping with a matching source and original line is yielded. + * @param {number} source + * @param {number} original_line + * @param {boolean} has_original_column + * @param {number} original_column + * @param {any} cb + */ + allGeneratedLocationsFor(source, original_line, has_original_column, original_column, cb) { + const ret = wasm.mappings_allGeneratedLocationsFor(this.__wbg_ptr, source, original_line, has_original_column, original_column, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Invoke `cb` on each mapping, in order of generated location. + * @param {any} cb + */ + byGeneratedLocation(cb) { + const ret = wasm.mappings_byGeneratedLocation(this.__wbg_ptr, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Invoke `cb` on each mapping that has original location information, in + * order of original location. + * @param {any} cb + */ + byOriginalLocation(cb) { + const ret = wasm.mappings_byOriginalLocation(this.__wbg_ptr, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Compute column spans for the mappings. + */ + computeColumnSpans() { + wasm.mappings_computeColumnSpans(this.__wbg_ptr); + } + /** + * Find the mapping for the given original location, if any exists, and + * invoke `cb` with it once. + * @param {number} source + * @param {number} original_line + * @param {number} original_column + * @param {number} bias + * @param {any} cb + */ + generatedLocationFor(source, original_line, original_column, bias, cb) { + const ret = wasm.mappings_generatedLocationFor(this.__wbg_ptr, source, original_line, original_column, bias, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Find the mapping for the given generated location, if any exists, and + * invoke `cb` with it once. + * @param {number} generated_line + * @param {number} generated_column + * @param {number} bias + * @param {any} cb + */ + originalLocationFor(generated_line, generated_column, bias, cb) { + const ret = wasm.mappings_originalLocationFor(this.__wbg_ptr, generated_line, generated_column, bias, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } +} +if (Symbol.dispose) Mappings.prototype[Symbol.dispose] = Mappings.prototype.free; + +/** + * Parse a source map's `mappings` string. + * + * On failure, rejects with the numeric `source_map_mappings::Error` code. + * Replaces the old `allocate_mappings` / `parse_mappings` / `get_last_error` + * trio: `wasm-bindgen` copies the string into wasm memory itself, so JS no + * longer has to hand-manage a buffer. + * @param {string} mappings + * @returns {Mappings} + */ +export function parseMappings(mappings) { + const ptr0 = passStringToWasm0(mappings, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parseMappings(ptr0, len0); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return Mappings.__wrap(ret[0]); +} +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_5d9e815e6fdf150f: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_call_3434fc50d361c5d3: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.call(arg1, arg2 >>> 0, arg3 >>> 0, arg4 !== 0, arg5 >>> 0, arg6 !== 0, arg7 >>> 0, arg8 >>> 0, arg9 >>> 0, arg10 !== 0, arg11 >>> 0); + }, arguments); }, + __wbindgen_generic_0000000000000001: function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./mappings_bg.js": import0, + }; +} + +const MappingsFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_mappings_free(ptr, 1)); + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; +} + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; +} + +let WASM_VECTOR_LEN = 0; + +let wasmModule, wasmInstance, wasm; +function __wbg_finalize_init(instance, module) { + wasmInstance = instance; + wasm = instance.exports; + wasmModule = module; + cachedUint8ArrayMemory0 = null; + wasm.__wbindgen_start(); + return wasm; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (!module.ok) { + throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`); + } + + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (module_or_path === undefined) { + throw new Error("You must provide the string URL or ArrayBuffer contents of lib/mappings.wasm by calling SourceMapConsumer.initialize() before using SourceMapConsumer"); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync, __wbg_init as default }; diff --git a/lib/mappings-glue.d.ts b/lib/mappings-glue.d.ts new file mode 100644 index 00000000..b060c57a --- /dev/null +++ b/lib/mappings-glue.d.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * A parsed `mappings` string, ready to be queried. + * + * JS owns this handle and must call `free()` on it when finished. + */ +export class Mappings { + private constructor(); + free(): void; + [Symbol.dispose](): void; + /** + * Find all mappings for the given original location and invoke `cb` on + * each of them. + * + * When `has_original_column` is false, `original_column` is ignored and + * every mapping with a matching source and original line is yielded. + */ + allGeneratedLocationsFor(source: number, original_line: number, has_original_column: boolean, original_column: number, cb: any): void; + /** + * Invoke `cb` on each mapping, in order of generated location. + */ + byGeneratedLocation(cb: any): void; + /** + * Invoke `cb` on each mapping that has original location information, in + * order of original location. + */ + byOriginalLocation(cb: any): void; + /** + * Compute column spans for the mappings. + */ + computeColumnSpans(): void; + /** + * Find the mapping for the given original location, if any exists, and + * invoke `cb` with it once. + */ + generatedLocationFor(source: number, original_line: number, original_column: number, bias: number, cb: any): void; + /** + * Find the mapping for the given generated location, if any exists, and + * invoke `cb` with it once. + */ + originalLocationFor(generated_line: number, generated_column: number, bias: number, cb: any): void; +} + +/** + * Parse a source map's `mappings` string. + * + * On failure, rejects with the numeric `source_map_mappings::Error` code. + * Replaces the old `allocate_mappings` / `parse_mappings` / `get_last_error` + * trio: `wasm-bindgen` copies the string into wasm memory itself, so JS no + * longer has to hand-manage a buffer. + */ +export function parseMappings(mappings: string): Mappings; diff --git a/lib/mappings-glue.js b/lib/mappings-glue.js new file mode 100644 index 00000000..e26cc87f --- /dev/null +++ b/lib/mappings-glue.js @@ -0,0 +1,253 @@ +/* @ts-self-types="./mappings-glue.d.ts" */ + +/** + * A parsed `mappings` string, ready to be queried. + * + * JS owns this handle and must call `free()` on it when finished. + */ +class Mappings { + static __wrap(ptr) { + const obj = Object.create(Mappings.prototype); + obj.__wbg_ptr = ptr; + MappingsFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + MappingsFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_mappings_free(ptr, 0); + } + /** + * Find all mappings for the given original location and invoke `cb` on + * each of them. + * + * When `has_original_column` is false, `original_column` is ignored and + * every mapping with a matching source and original line is yielded. + * @param {number} source + * @param {number} original_line + * @param {boolean} has_original_column + * @param {number} original_column + * @param {any} cb + */ + allGeneratedLocationsFor(source, original_line, has_original_column, original_column, cb) { + const ret = wasm.mappings_allGeneratedLocationsFor(this.__wbg_ptr, source, original_line, has_original_column, original_column, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Invoke `cb` on each mapping, in order of generated location. + * @param {any} cb + */ + byGeneratedLocation(cb) { + const ret = wasm.mappings_byGeneratedLocation(this.__wbg_ptr, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Invoke `cb` on each mapping that has original location information, in + * order of original location. + * @param {any} cb + */ + byOriginalLocation(cb) { + const ret = wasm.mappings_byOriginalLocation(this.__wbg_ptr, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Compute column spans for the mappings. + */ + computeColumnSpans() { + wasm.mappings_computeColumnSpans(this.__wbg_ptr); + } + /** + * Find the mapping for the given original location, if any exists, and + * invoke `cb` with it once. + * @param {number} source + * @param {number} original_line + * @param {number} original_column + * @param {number} bias + * @param {any} cb + */ + generatedLocationFor(source, original_line, original_column, bias, cb) { + const ret = wasm.mappings_generatedLocationFor(this.__wbg_ptr, source, original_line, original_column, bias, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * Find the mapping for the given generated location, if any exists, and + * invoke `cb` with it once. + * @param {number} generated_line + * @param {number} generated_column + * @param {number} bias + * @param {any} cb + */ + originalLocationFor(generated_line, generated_column, bias, cb) { + const ret = wasm.mappings_originalLocationFor(this.__wbg_ptr, generated_line, generated_column, bias, cb); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } +} +if (Symbol.dispose) Mappings.prototype[Symbol.dispose] = Mappings.prototype.free; +exports.Mappings = Mappings; + +/** + * Parse a source map's `mappings` string. + * + * On failure, rejects with the numeric `source_map_mappings::Error` code. + * Replaces the old `allocate_mappings` / `parse_mappings` / `get_last_error` + * trio: `wasm-bindgen` copies the string into wasm memory itself, so JS no + * longer has to hand-manage a buffer. + * @param {string} mappings + * @returns {Mappings} + */ +function parseMappings(mappings) { + const ptr0 = passStringToWasm0(mappings, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parseMappings(ptr0, len0); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return Mappings.__wrap(ret[0]); +} +exports.parseMappings = parseMappings; +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg___wbindgen_throw_5d9e815e6fdf150f: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_call_3434fc50d361c5d3: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.call(arg1, arg2 >>> 0, arg3 >>> 0, arg4 !== 0, arg5 >>> 0, arg6 !== 0, arg7 >>> 0, arg8 >>> 0, arg9 >>> 0, arg10 !== 0, arg11 >>> 0); + }, arguments); }, + __wbindgen_generic_0000000000000001: function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./mappings_bg.js": import0, + }; +} + +const MappingsFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_mappings_free(ptr, 1)); + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; +} + +function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +function decodeText(ptr, len) { + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; +} + +let WASM_VECTOR_LEN = 0; + +const wasmPath = `${__dirname}/mappings.wasm`; +const wasmBytes = require('fs').readFileSync(wasmPath); +const wasmModule = new WebAssembly.Module(wasmBytes); +let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports()); +let wasm = wasmInstance.exports; +wasm.__wbindgen_start(); diff --git a/lib/mappings.wasm b/lib/mappings.wasm index cdcc2958..7fc7d86e 100644 Binary files a/lib/mappings.wasm and b/lib/mappings.wasm differ diff --git a/lib/read-wasm-browser.js b/lib/read-wasm-browser.js index a4fb29c2..f245be6e 100644 --- a/lib/read-wasm-browser.js +++ b/lib/read-wasm-browser.js @@ -1,21 +1,38 @@ "use strict"; let mappingsWasm = null; +let cachedGlue = null; module.exports = function readWasm() { - if (typeof mappingsWasm === "string") { - return fetch(mappingsWasm).then(response => response.arrayBuffer()); + if ( + typeof mappingsWasm !== "string" && + !(mappingsWasm instanceof ArrayBuffer) + ) { + throw new Error( + "You must provide the string URL or ArrayBuffer contents " + + "of lib/mappings.wasm by calling " + + "SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) " + + "before using SourceMapConsumer" + ); } - if (mappingsWasm instanceof ArrayBuffer) { - return Promise.resolve(mappingsWasm); + + if (cachedGlue) { + return cachedGlue; } - throw new Error( - "You must provide the string URL or ArrayBuffer contents " + - "of lib/mappings.wasm by calling " + - "SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) " + - "before using SourceMapConsumer" - ); + // The browser glue is an ES module; `import()` keeps this file loadable as + // CommonJS. `default` is wasm-bindgen's async init, which accepts either a + // URL to fetch or the module bytes directly. + cachedGlue = import("./mappings-glue-browser.mjs") + .then(glue => + glue.default({ module_or_path: mappingsWasm }).then(() => glue) + ) + .catch(e => { + cachedGlue = null; + throw e; + }); + + return cachedGlue; }; module.exports.initialize = input => { diff --git a/lib/read-wasm.js b/lib/read-wasm.js index 2ce01a75..72afb6f7 100644 --- a/lib/read-wasm.js +++ b/lib/read-wasm.js @@ -3,21 +3,10 @@ // Note: This file is replaced with "read-wasm-browser.js" when this module is // bundled with a packager that takes package.json#browser fields into account. -const fs = require("fs"); -const path = require("path"); - module.exports = function readWasm() { - return new Promise((resolve, reject) => { - const wasmPath = path.join(__dirname, "mappings.wasm"); - fs.readFile(wasmPath, null, (error, data) => { - if (error) { - reject(error); - return; - } - - resolve(data.buffer); - }); - }); + // The generated Node glue reads `mappings.wasm` off disk and instantiates it + // itself, so requiring it lazily here is all the loading we need to do. + return Promise.resolve(require("./mappings-glue.js")); }; module.exports.initialize = _ => { diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 7381a668..17ef92b1 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -223,7 +223,7 @@ class BasicSourceMapConsumer extends SourceMapConsumer { that.x_google_ignoreList = x_google_ignoreList; that._computedColumnSpans = false; - that._mappingsPtr = 0; + that._wasmMappings = null; that._wasm = null; return wasm().then(w => { @@ -290,12 +290,12 @@ class BasicSourceMapConsumer extends SourceMapConsumer { return this._absoluteSources.toArray(); } - _getMappingsPtr() { - if (this._mappingsPtr === 0) { + _getWasmMappings() { + if (this._wasmMappings === null) { this._parseMappings(); } - return this._mappingsPtr; + return this._wasmMappings; } /** @@ -304,25 +304,10 @@ class BasicSourceMapConsumer extends SourceMapConsumer { * `this.__originalMappings` properties). */ _parseMappings() { - const aStr = this._mappings; - const size = aStr.length; - - // Interpret signed result of allocate_mappings as unsigned, otherwise - // addresses higher than 2GB will be negative. - const mappingsBufPtr = this._wasm.exports.allocate_mappings(size) >>> 0; - const mappingsBuf = new Uint8Array( - this._wasm.exports.memory.buffer, - mappingsBufPtr, - size - ); - for (let i = 0; i < size; i++) { - mappingsBuf[i] = aStr.charCodeAt(i); - } - - const mappingsPtr = this._wasm.exports.parse_mappings(mappingsBufPtr); - - if (!mappingsPtr) { - const error = this._wasm.exports.get_last_error(); + let mappings; + try { + mappings = this._wasm.parseMappings(this._mappings); + } catch (error) { let msg = `Error parsing mappings (code ${error}): `; // XXX: keep these error codes in sync with `wasm-mappings`. @@ -348,41 +333,38 @@ class BasicSourceMapConsumer extends SourceMapConsumer { throw new Error(msg); } - this._mappingsPtr = mappingsPtr; + this._wasmMappings = mappings; } eachMapping(aCallback, aContext, aOrder) { const context = aContext || null; const order = aOrder || SourceMapConsumer.GENERATED_ORDER; - this._wasm.withMappingCallback( - mapping => { - if (mapping.source !== null) { - mapping.source = this._absoluteSources.at(mapping.source); - - if (mapping.name !== null) { - mapping.name = this._names.at(mapping.name); - } - } - if (this._computedColumnSpans && mapping.lastGeneratedColumn === null) { - mapping.lastGeneratedColumn = Infinity; - } + const callback = wasm.mappingCallback(mapping => { + if (mapping.source !== null) { + mapping.source = this._absoluteSources.at(mapping.source); - aCallback.call(context, mapping); - }, - () => { - switch (order) { - case SourceMapConsumer.GENERATED_ORDER: - this._wasm.exports.by_generated_location(this._getMappingsPtr()); - break; - case SourceMapConsumer.ORIGINAL_ORDER: - this._wasm.exports.by_original_location(this._getMappingsPtr()); - break; - default: - throw new Error("Unknown order of iteration."); + if (mapping.name !== null) { + mapping.name = this._names.at(mapping.name); } } - ); + if (this._computedColumnSpans && mapping.lastGeneratedColumn === null) { + mapping.lastGeneratedColumn = Infinity; + } + + aCallback.call(context, mapping); + }); + + switch (order) { + case SourceMapConsumer.GENERATED_ORDER: + this._getWasmMappings().byGeneratedLocation(callback); + break; + case SourceMapConsumer.ORIGINAL_ORDER: + this._getWasmMappings().byOriginalLocation(callback); + break; + default: + throw new Error("Unknown order of iteration."); + } } allGeneratedPositionsFor(aArgs) { @@ -405,8 +387,12 @@ class BasicSourceMapConsumer extends SourceMapConsumer { const mappings = []; - this._wasm.withMappingCallback( - m => { + this._getWasmMappings().allGeneratedLocationsFor( + source, + originalLine - 1, + "column" in aArgs, + originalColumn, + wasm.mappingCallback(m => { let lastColumn = m.lastGeneratedColumn; if (this._computedColumnSpans && lastColumn === null) { lastColumn = Infinity; @@ -416,25 +402,16 @@ class BasicSourceMapConsumer extends SourceMapConsumer { column: m.generatedColumn, lastColumn, }); - }, - () => { - this._wasm.exports.all_generated_locations_for( - this._getMappingsPtr(), - source, - originalLine - 1, - "column" in aArgs, - originalColumn - ); - } + }) ); return mappings; } destroy() { - if (this._mappingsPtr !== 0) { - this._wasm.exports.free_mappings(this._mappingsPtr); - this._mappingsPtr = 0; + if (this._wasmMappings !== null) { + this._wasmMappings.free(); + this._wasmMappings = null; } } @@ -447,7 +424,7 @@ class BasicSourceMapConsumer extends SourceMapConsumer { return; } - this._wasm.exports.compute_column_spans(this._getMappingsPtr()); + this._getWasmMappings().computeColumnSpans(); this._computedColumnSpans = true; } @@ -499,16 +476,11 @@ class BasicSourceMapConsumer extends SourceMapConsumer { } let mapping; - this._wasm.withMappingCallback( - m => (mapping = m), - () => { - this._wasm.exports.original_location_for( - this._getMappingsPtr(), - needle.generatedLine - 1, - needle.generatedColumn, - bias - ); - } + this._getWasmMappings().originalLocationFor( + needle.generatedLine - 1, + needle.generatedColumn, + bias, + wasm.mappingCallback(m => (mapping = m)) ); if (mapping) { @@ -640,17 +612,12 @@ class BasicSourceMapConsumer extends SourceMapConsumer { } let mapping; - this._wasm.withMappingCallback( - m => (mapping = m), - () => { - this._wasm.exports.generated_location_for( - this._getMappingsPtr(), - needle.source, - needle.originalLine - 1, - needle.originalColumn, - bias - ); - } + this._getWasmMappings().generatedLocationFor( + needle.source, + needle.originalLine - 1, + needle.originalColumn, + bias, + wasm.mappingCallback(m => (mapping = m)) ); if (mapping) { diff --git a/lib/url.js b/lib/url.js index 8bb71b6a..acb0d37d 100644 --- a/lib/url.js +++ b/lib/url.js @@ -6,8 +6,7 @@ */ "use strict"; -// Note: This file is overridden in the 'package.json#browser' field to -// substitute lib/url-browser.js instead. - -// Use the URL global for Node 10, and the 'url' module for Node 8. -module.exports = typeof URL === "function" ? URL : require("url").URL; +// `URL` has been a global since Node 10 and this package requires Node >= 18, +// so there is no need to fall back to the 'url' core module. Requiring it would +// also break bundlers that no longer polyfill Node core modules (webpack >= 5). +module.exports = URL; diff --git a/lib/wasm.js b/lib/wasm.js index 3091d9ee..5ff17f33 100644 --- a/lib/wasm.js +++ b/lib/wasm.js @@ -13,6 +13,52 @@ function Mapping() { this.name = null; } +/** + * Wrap a `mapping => {}` callback in the flat-argument form the wasm module + * invokes. Building the `Mapping` here (rather than in Rust) keeps every + * mapping on a single hidden class. + */ +function mappingCallback(callback) { + return function ( + generatedLine, + generatedColumn, + + hasLastGeneratedColumn, + lastGeneratedColumn, + + hasOriginal, + source, + originalLine, + originalColumn, + + hasName, + name + ) { + const mapping = new Mapping(); + // JS uses 1-based line numbers, wasm uses 0-based. + mapping.generatedLine = generatedLine + 1; + mapping.generatedColumn = generatedColumn; + + if (hasLastGeneratedColumn) { + // JS uses inclusive last generated column, wasm uses exclusive. + mapping.lastGeneratedColumn = lastGeneratedColumn - 1; + } + + if (hasOriginal) { + mapping.source = source; + // JS uses 1-based line numbers, wasm uses 0-based. + mapping.originalLine = originalLine + 1; + mapping.originalColumn = originalColumn; + + if (hasName) { + mapping.name = name; + } + } + + callback(mapping); + }; +} + let cachedWasm = null; module.exports = function wasm() { @@ -20,119 +66,12 @@ module.exports = function wasm() { return cachedWasm; } - const callbackStack = []; - - cachedWasm = readWasm() - .then(buffer => { - return WebAssembly.instantiate(buffer, { - env: { - mapping_callback( - generatedLine, - generatedColumn, - - hasLastGeneratedColumn, - lastGeneratedColumn, - - hasOriginal, - source, - originalLine, - originalColumn, - - hasName, - name - ) { - const mapping = new Mapping(); - // JS uses 1-based line numbers, wasm uses 0-based. - mapping.generatedLine = generatedLine + 1; - mapping.generatedColumn = generatedColumn; - - if (hasLastGeneratedColumn) { - // JS uses inclusive last generated column, wasm uses exclusive. - mapping.lastGeneratedColumn = lastGeneratedColumn - 1; - } - - if (hasOriginal) { - mapping.source = source; - // JS uses 1-based line numbers, wasm uses 0-based. - mapping.originalLine = originalLine + 1; - mapping.originalColumn = originalColumn; - - if (hasName) { - mapping.name = name; - } - } - - callbackStack[callbackStack.length - 1](mapping); - }, - - start_all_generated_locations_for() { - console.time("all_generated_locations_for"); - }, - end_all_generated_locations_for() { - console.timeEnd("all_generated_locations_for"); - }, - - start_compute_column_spans() { - console.time("compute_column_spans"); - }, - end_compute_column_spans() { - console.timeEnd("compute_column_spans"); - }, - - start_generated_location_for() { - console.time("generated_location_for"); - }, - end_generated_location_for() { - console.timeEnd("generated_location_for"); - }, - - start_original_location_for() { - console.time("original_location_for"); - }, - end_original_location_for() { - console.timeEnd("original_location_for"); - }, - - start_parse_mappings() { - console.time("parse_mappings"); - }, - end_parse_mappings() { - console.timeEnd("parse_mappings"); - }, - - start_sort_by_generated_location() { - console.time("sort_by_generated_location"); - }, - end_sort_by_generated_location() { - console.timeEnd("sort_by_generated_location"); - }, - - start_sort_by_original_location() { - console.time("sort_by_original_location"); - }, - end_sort_by_original_location() { - console.timeEnd("sort_by_original_location"); - }, - }, - }); - }) - .then(Wasm => { - return { - exports: Wasm.instance.exports, - withMappingCallback: (mappingCallback, f) => { - callbackStack.push(mappingCallback); - try { - f(); - } finally { - callbackStack.pop(); - } - }, - }; - }) - .then(null, e => { - cachedWasm = null; - throw e; - }); + cachedWasm = readWasm().then(null, e => { + cachedWasm = null; + throw e; + }); return cachedWasm; }; + +module.exports.mappingCallback = mappingCallback; diff --git a/package-lock.json b/package-lock.json index 1ca38a41..51caa8fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "prettier": "^2.7.1" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/@bcoe/v8-coverage": { diff --git a/package.json b/package.json index c8dc010b..682a42be 100644 --- a/package.json +++ b/package.json @@ -57,11 +57,11 @@ "lib/" ], "engines": { - "node": ">= 12" + "node": ">= 18" }, "license": "BSD-3-Clause", "scripts": { - "lint": "eslint --fix *.js lib/ test/ --ignore-pattern 'test/source-map-tests/**'", + "lint": "eslint --fix *.js lib/ test/ --ignore-pattern 'test/source-map-tests/**' --ignore-pattern 'lib/mappings-glue*'", "test": "git submodule update --init --recursive; node test/run-tests.js", "coverage": "c8 --reporter=text --reporter=html npm test", "prettier": "prettier --write .", diff --git a/test/test-source-map-consumer.js b/test/test-source-map-consumer.js index dc40ba28..8cf50988 100644 --- a/test/test-source-map-consumer.js +++ b/test/test-source-map-consumer.js @@ -2143,11 +2143,11 @@ exports["test SourceMapConsumer.with"] = async function (assert) { await new Promise(r => setTimeout(r, 1)); // Should not have parsed and allocated mappings yet. - assert.equal(c._mappingsPtr, 0); + assert.equal(c._wasmMappings, null); // Force the mappings to be parsed and assert that we allocated mappings. c.eachMapping(_ => {}); - assert.ok(c._mappingsPtr != 0); + assert.ok(c._wasmMappings != null); return 6; } @@ -2157,7 +2157,7 @@ exports["test SourceMapConsumer.with"] = async function (assert) { assert.equal(six, 6); // At the end of `with`, we destroyed the mappings. - assert.equal(consumer._mappingsPtr, 0); + assert.equal(consumer._wasmMappings, null); }; exports["test SourceMapConsumer.with and exceptions"] = async function ( @@ -2169,10 +2169,10 @@ exports["test SourceMapConsumer.with and exceptions"] = async function ( try { await SourceMapConsumer.with(util.testMap, null, async function (c) { consumer = c; - assert.equal(c._mappingsPtr, 0); + assert.equal(c._wasmMappings, null); c.eachMapping(_ => {}); - assert.ok(c._mappingsPtr != 0); + assert.ok(c._wasmMappings != null); throw 6; }); @@ -2181,7 +2181,7 @@ exports["test SourceMapConsumer.with and exceptions"] = async function ( } assert.equal(error, 6); - assert.equal(consumer._mappingsPtr, 0); + assert.equal(consumer._wasmMappings, null); }; exports["test a mapping at the boundary of indexed source map offset"] = diff --git a/wasm-mappings/.gitignore b/wasm-mappings/.gitignore index 143b1ca0..48292f4b 100644 --- a/wasm-mappings/.gitignore +++ b/wasm-mappings/.gitignore @@ -1,4 +1,5 @@ /target/ **/*.rs.bk -Cargo.lock +source-map-mappings-wasm-api/pkg-node/ +source-map-mappings-wasm-api/pkg-web/ diff --git a/wasm-mappings/CONTRIBUTING.md b/wasm-mappings/CONTRIBUTING.md index d67cd208..c38841c3 100644 --- a/wasm-mappings/CONTRIBUTING.md +++ b/wasm-mappings/CONTRIBUTING.md @@ -8,28 +8,35 @@ To build the core library for the host target (for use with testing): $ cargo build ``` -To build for WebAssembly, ensure that you have the `wasm32-unknown-unknown` target: +To build the WebAssembly module and its JS glue, use the build script in this +directory: ``` -$ rustup update -$ rustup target add wasm32-unknown-unknown --toolchain nightly +$ ./build.sh ``` -Then, cross compile to a `.wasm` file via the WebAssembly API crate: +That runs [`wasm-pack`](https://wasm-bindgen.github.io/wasm-pack/) twice -- once +for the `nodejs` target and once for `web` -- and installs the results into +`../lib/`. Both targets produce an identical `.wasm`; only the JS glue +differs, and the script fails if they ever diverge. -``` -$ cd source-map-mappings-wasm-api/ -$ ./build.py -o output.wasm -``` +You need two tools besides Rust: -The `build.py` script handles shrinking the size of the resulting `.wasm` file -for you, with `wasm-gc`, `wasm-snip`, and `wasm-opt`. +- `wasm-pack` 0.15.0 (`cargo install wasm-pack --version 0.15.0 --locked`), + which vendors its own `wasm-bindgen`. +- `wasm-opt` from [binaryen](https://github.com/WebAssembly/binaryen) version + 132 on your `PATH`. `build.sh` refuses to run with any other version, because + without one wasm-pack downloads an older binaryen whose output differs. -For more details, run: +The Rust toolchain version is pinned in `rust-toolchain.toml`, including the +`wasm32-unknown-unknown` target, so `rustup` will fetch whatever is missing. -``` -$ ./build.py --help -``` +Set `PROFILING=1` to enable the `profiling` cargo feature. + +The generated artifacts are committed to the repository, and the `wasm-dist` +CI job rebuilds them and fails if they differ byte-for-byte. If you change +anything under `wasm-mappings/`, re-run `./build.sh` and commit the result in +the same change. ## Testing @@ -41,18 +48,13 @@ $ cargo test ## Automatic code formatting -We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a +We use [`rustfmt`](https://github.com/rust-lang/rustfmt) to enforce a consistent code style across the whole code base. -You can install the latest version of `rustfmt` with this command: - ``` -$ rustup update nightly -$ cargo install -f rustfmt-nightly +$ rustup component add rustfmt ``` -Ensure that `~/.cargo/bin` is on your path. - Once that is taken care of, you can (re)format all code by running this command: ``` diff --git a/wasm-mappings/Cargo.lock b/wasm-mappings/Cargo.lock new file mode 100644 index 00000000..0015f32b --- /dev/null +++ b/wasm-mappings/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "quickcheck" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c589f335db0f6aaa168a7cd27b1fc6920f5e1470c804f814d9cd6e62a0f70b" +dependencies = [ + "rand", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "source-map-mappings" +version = "0.5.0" +dependencies = [ + "quickcheck", + "vlq", +] + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" diff --git a/wasm-mappings/Cargo.toml b/wasm-mappings/Cargo.toml index 6e075f37..d9e5a13b 100644 --- a/wasm-mappings/Cargo.toml +++ b/wasm-mappings/Cargo.toml @@ -8,6 +8,7 @@ categories = [ "parser-implementations", ] description = "Parse the `mappings` string from a source map." +edition = "2024" keywords = [ "sourcemap", "source", @@ -21,10 +22,10 @@ repository = "https://github.com/mozilla/source-map" version = "0.5.0" [dependencies] -rand = "0.4.1" vlq = "0.5.1" [dev-dependencies] -quickcheck = "0.5.0" +quickcheck = { version = "1.1", default-features = false } + [profile.release] -debug = true +debug = false diff --git a/wasm-mappings/build.sh b/wasm-mappings/build.sh new file mode 100755 index 00000000..4fb1ab9c --- /dev/null +++ b/wasm-mappings/build.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# +# Build `lib/mappings.wasm` and its wasm-bindgen glue. +# +# Requires `wasm-pack` (https://wasm-bindgen.github.io/wasm-pack/), which vendors +# its own `wasm-bindgen`, and binaryen's `wasm-opt` at exactly +# $WASM_OPT_VERSION on PATH. wasm-pack prefers a `wasm-opt` from PATH and only +# downloads its own (an older binaryen) when there is none, and different +# binaryen versions produce different bytes, so the version is pinned here +# rather than left to whatever happens to be installed. +# +# Two targets are built because the package is CommonJS but also supports +# browsers via `package.json#browser`: +# +# nodejs -> lib/mappings-glue.js (CJS, reads mappings.wasm off disk) +# web -> lib/mappings-glue-browser.mjs (ESM, init() takes a URL or bytes) +# +# Both emit the same `.wasm`; only the glue differs. +# +# Set PROFILING=1 to enable the `profiling` cargo feature. + +set -eu + +# Keep in sync with BINARYEN_VERSION in .github/workflows/cargo.yml. +WASM_OPT_VERSION=132 +if ! WASM_OPT_ACTUAL="$(wasm-opt --version 2>/dev/null)"; then + echo "error: wasm-opt (binaryen version $WASM_OPT_VERSION) not found on PATH" >&2 + exit 1 +fi +case "$WASM_OPT_ACTUAL" in + "wasm-opt version $WASM_OPT_VERSION "*|"wasm-opt version $WASM_OPT_VERSION") ;; + *) + echo "error: need binaryen version $WASM_OPT_VERSION, found: $WASM_OPT_ACTUAL" >&2 + exit 1 + ;; +esac + +cd "$(dirname "$0")" +REPO_ROOT="$(pwd)/.." +CRATE_DIR="source-map-mappings-wasm-api" + +CARGO_ARGS="" +if [ "${PROFILING:-}" = "1" ]; then + CARGO_ARGS="--features profiling" +fi + +cd "$CRATE_DIR" +rm -rf pkg-node pkg-web + +# Panic locations embed absolute source paths in the binary, which differ +# between machines (checkout location, $CARGO_HOME, and whether rustup's +# `rust-src` component is installed) and would make the output depend on where +# it was built. Remap them to fixed prefixes. `trim-paths` would do this, but +# it is not stable yet. Run from $CRATE_DIR so rustc is the pinned toolchain. +REPO_ROOT_ABS="$(cd "$REPO_ROOT" && pwd -P)" +RUST_SRC="$(rustc --print sysroot)/lib/rustlib/src/rust" +RUSTC_COMMIT="$(rustc -vV | sed -n 's/^commit-hash: //p')" +CARGO_REGISTRY="${CARGO_HOME:-$HOME/.cargo}/registry/src" +SEP="$(printf '\037')" +export CARGO_ENCODED_RUSTFLAGS="--remap-path-prefix=$REPO_ROOT_ABS=/source-map${SEP}--remap-path-prefix=$RUST_SRC=/rustc/$RUSTC_COMMIT${SEP}--remap-path-prefix=$CARGO_REGISTRY=/cargo/registry/src" + +# shellcheck disable=SC2086 +wasm-pack build --release --target nodejs \ + --out-dir pkg-node --out-name mappings --no-pack -- $CARGO_ARGS +# shellcheck disable=SC2086 +wasm-pack build --release --target web \ + --out-dir pkg-web --out-name mappings --no-pack -- $CARGO_ARGS + +# Both targets must agree on the binary; if they ever diverge, shipping one +# `mappings.wasm` for both would be silently wrong. +cmp pkg-node/mappings_bg.wasm pkg-web/mappings_bg.wasm + +LIB="$REPO_ROOT/lib" +cp pkg-node/mappings_bg.wasm "$LIB/mappings.wasm" + +# The glue looks for `mappings_bg.wasm`; we ship it as `mappings.wasm` so the +# URL documented for `SourceMapConsumer.initialize` keeps working. +sed -e "s/mappings_bg\.wasm/mappings.wasm/g" \ + -e 's|"\./mappings\.d\.ts"|"./mappings-glue.d.ts"|' \ + pkg-node/mappings.js > "$LIB/mappings-glue.js" +# `read-wasm-browser.js` always passes the module bytes or URL explicitly, so +# wasm-bindgen's `new URL(..., import.meta.url)` fallback is unreachable here. +# Replace it with a throw: `import.meta` is a hard parse error in webpack 4 and +# other pre-2020 bundlers, which would otherwise break any consumer using them. +sed -e "s/mappings_bg\.wasm/mappings.wasm/g" \ + -e 's|"\./mappings\.d\.ts"|"./mappings-glue-browser.d.mts"|' \ + -e 's|^.*import\.meta\.url.*$| throw new Error("You must provide the string URL or ArrayBuffer contents of lib/mappings.wasm by calling SourceMapConsumer.initialize() before using SourceMapConsumer");|' \ + pkg-web/mappings.js > "$LIB/mappings-glue-browser.mjs" + +cp pkg-node/mappings.d.ts "$LIB/mappings-glue.d.ts" +cp pkg-web/mappings.d.ts "$LIB/mappings-glue-browser.d.mts" + +# Nothing should still point at the wasm-pack-internal name. +! grep -q "mappings_bg\.wasm" "$LIB/mappings-glue.js" "$LIB/mappings-glue-browser.mjs" +! grep -q "import\.meta" "$LIB/mappings-glue-browser.mjs" + +echo "built $(wc -c < "$LIB/mappings.wasm") byte lib/mappings.wasm" diff --git a/wasm-mappings/ci/script.sh b/wasm-mappings/ci/script.sh index e59fe4a0..1ac116ec 100755 --- a/wasm-mappings/ci/script.sh +++ b/wasm-mappings/ci/script.sh @@ -2,21 +2,25 @@ set -eux +# Everything below expects to run from wasm-mappings/, both for the relative +# paths and so that rustup picks up wasm-mappings/rust-toolchain.toml. +cd "$(dirname "$0")/.." + case "$JOB" in "test") cargo test ;; "bench") + # The benches use `#![feature(test)]`, so they need nightly rather than + # the stable toolchain pinned in rust-toolchain.toml. An explicit + # RUSTUP_TOOLCHAIN (as set by CI) still wins. + export RUSTUP_TOOLCHAIN="${RUSTUP_TOOLCHAIN:-nightly}" cargo bench ;; "wasm") rustup target add wasm32-unknown-unknown cd source-map-mappings-wasm-api/ - # Non-release builds are broken for wasm32-unknown-unknown targets right now. - # cargo build --target wasm32-unknown-unknown - # test -f target/wasm32-unknown-unknown/debug/source_map_mappings_wasm_api.wasm - cargo build --release --target wasm32-unknown-unknown test -f target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm @@ -24,6 +28,20 @@ case "$JOB" in cargo build --release --target wasm32-unknown-unknown --features profiling test -f target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm ;; + "wasm-dist") + # Rebuild the committed wasm binary and its glue from source and fail + # if anything differs. Without this, the shipped artifacts are never + # checked against the Rust sources they supposedly came from. + rustup target add wasm32-unknown-unknown + ./build.sh + cd .. + git diff --stat --exit-code -- \ + lib/mappings.wasm \ + lib/mappings-glue.js \ + lib/mappings-glue.d.ts \ + lib/mappings-glue-browser.mjs \ + lib/mappings-glue-browser.d.mts + ;; *) echo "Unknown \$JOB = '$JOB'" exit 1 diff --git a/wasm-mappings/rust-toolchain.toml b/wasm-mappings/rust-toolchain.toml new file mode 100644 index 00000000..0a801950 --- /dev/null +++ b/wasm-mappings/rust-toolchain.toml @@ -0,0 +1,12 @@ +# Pinned so that `lib/mappings.wasm` is byte-reproducible: the `wasm-dist` CI +# job rebuilds the committed binary and fails if it differs, which only works +# if everyone builds with the same rustc. Bump deliberately, and commit the +# regenerated `lib/mappings.wasm` in the same change. +# +# Note: the `cargo.yml` test matrix overrides this by setting RUSTUP_TOOLCHAIN +# (a `rustup override set` on the repo root would not win over this file), so +# stable/beta/nightly coverage is unaffected. `JOB=bench ./ci/script.sh` +# defaults to nightly for the same reason. +[toolchain] +channel = "1.98.1" +targets = ["wasm32-unknown-unknown"] diff --git a/wasm-mappings/source-map-mappings-wasm-api/Cargo.lock b/wasm-mappings/source-map-mappings-wasm-api/Cargo.lock new file mode 100644 index 00000000..7a1116fd --- /dev/null +++ b/wasm-mappings/source-map-mappings-wasm-api/Cargo.lock @@ -0,0 +1,128 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "source-map-mappings" +version = "0.5.0" +dependencies = [ + "vlq", +] + +[[package]] +name = "source-map-mappings-wasm-api" +version = "0.5.0" +dependencies = [ + "source-map-mappings", + "wasm-bindgen", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] diff --git a/wasm-mappings/source-map-mappings-wasm-api/Cargo.toml b/wasm-mappings/source-map-mappings-wasm-api/Cargo.toml index 43683b01..8c025db9 100644 --- a/wasm-mappings/source-map-mappings-wasm-api/Cargo.toml +++ b/wasm-mappings/source-map-mappings-wasm-api/Cargo.toml @@ -1,6 +1,7 @@ [package] authors = ["Nick Fitzgerald ", "Tom Tromey "] description = "Exported WebAssembly API for the `source-map-mappings` crate." +edition = "2024" license = "BSD-3-Clause" name = "source-map-mappings-wasm-api" readme = "../README.md" @@ -9,6 +10,7 @@ version = "0.5.0" [dependencies] source-map-mappings = { version = "0.5.0", path = ".." } +wasm-bindgen = "0.2" [features] profiling = [] @@ -17,4 +19,8 @@ profiling = [] crate-type = ["cdylib"] [profile.release] -debug = true +debug = false +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" diff --git a/wasm-mappings/source-map-mappings-wasm-api/build.py b/wasm-mappings/source-map-mappings-wasm-api/build.py deleted file mode 100755 index 7ff2aab1..00000000 --- a/wasm-mappings/source-map-mappings-wasm-api/build.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import re -import subprocess -import sys - -DESC = """ - -Build, trim, and optimize the `.wasm` file for inclusion in the -`mozilla/source-map` library. - -Requires: - -- wasm-nm: https://github.com/fitzgen/wasm-nm -- wasm-gc: https://github.com/alexcrichton/wasm-gc -- wasm-snip: https://github.com/fitzgen/wasm-snip -- wasm-opt: https://github.com/WebAssembly/binaryen - -""" - -parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=DESC) - -parser.add_argument( - "-g", - "--debug", - action="store_true", - help="Include debug info (the \"name\" section) in the final `.wasm` file.") - -parser.add_argument( - "-p", - "--profiling", - action="store_true", - help="Enable the `profiling` cargo feature.") - -parser.add_argument( - "-o", - "--output", - type=str, - default=None, - help="The path to write the output `.wasm` file to. If not supplied, the `.wasm` file is written to `stdout`.") - -parser.add_argument( - "--no-wasm-opt", - dest="wasm_opt", - action="store_false", - help="Do not run `wasm-opt`.") - -parser.add_argument( - "--no-wasm-gc", - dest="wasm_gc", - action="store_false", - help="Do not run `wasm-gc`.") - -parser.add_argument( - "--no-wasm-snip", - dest="wasm_snip", - action="store_false", - help="Do not run `wasm-snip`.") - -def decode(f): - return f.decode(encoding="utf-8", errors="ignore") - -def run(cmd, **kwargs): - sys.stderr.write(str(cmd) + "\n") - - if "stdout" not in kwargs: - kwargs["stdout"] = subprocess.PIPE - child = subprocess.run(cmd, **kwargs) - if child.returncode != 0: - raise Exception("{} did not exit OK".format(str(cmd))) - return decode(child.stdout) - -def add_path_ext_prefix(path, prefix): - (root, ext) = os.path.splitext(path) - return root + "." + prefix + ext - -def build(args): - cmd = ["cargo", "build", "--release", "--target", "wasm32-unknown-unknown"] - if args.profiling: - cmd.extend(["--features", "profiling"]) - run(cmd) - return "./target/wasm32-unknown-unknown/release/source_map_mappings_wasm_api.wasm" - -def wasm_gc(args, wasm_path): - if not args.wasm_gc: - return wasm_path - - out_path = add_path_ext_prefix(wasm_path, "gc") - run(["wasm-gc", wasm_path, out_path]) - return out_path - -SHOULD_SNIP = [ - re.compile(r".*(std|core)(9|::)panicking.*"), - re.compile(r".*(std|core)(3|::)fmt.*"), - re.compile(r".*core(6|::)option(13|::)expect_failed.*"), - re.compile(r".*core(5|::)slice(\d+|::)slice_index_.*_fail.*"), - re.compile(r".*core(3|::)str(\d+|::)slice_.*_fail.*"), - re.compile(r".*core(6|::)result(13|::)unwrap_failed.*"), - re.compile(r".*std(6|::)thread(5|::)local.*"), - re.compile(r".*std(2|::)io(5|::).*"), - re.compile(r"__.*2"), - re.compile(r".*(std|core)(5|::)error.*"), - re.compile(r".*(std|core)(3|::)any(3|::)Any.*"), -] - -def wasm_snip(args, wasm_path): - if not args.wasm_snip: - return wasm_path - - out_path = add_path_ext_prefix(wasm_path, "snip") - - private_functions = run(["wasm-nm", "-j", wasm_path]).splitlines() - - snip_functions = set() - for snip in SHOULD_SNIP: - snip_functions.update(filter(lambda f: re.match(snip, f), - private_functions)) - - run(["wasm-snip", "-o", out_path, wasm_path, *snip_functions]), - return out_path - -def wasm_opt(args, wasm_path): - if not args.wasm_opt: - return wasm_path - - out_path = add_path_ext_prefix(wasm_path, "opt") - - cmd = [ - "wasm-opt", - "-O3", - "-Oz", - "--duplicate-function-elimination", - "-o", out_path, - wasm_path - ] - if args.debug: - cmd.append("-g") - run(cmd) - return out_path - -def main(): - args = parser.parse_args() - os.chdir(os.path.dirname(sys.argv[0])) - - wasm_path = build(args) - wasm_path = wasm_gc(args, wasm_path) - wasm_path = wasm_snip(args, wasm_path) - # GC again after snipping. - wasm_path = wasm_gc(args, wasm_path) - wasm_path = wasm_opt(args, wasm_path) - - if args.output: - run(["cp", wasm_path, args.output]) - else: - run(["cat", wasm_path], stdout=subprocess.STDOUT) - -if __name__ == "__main__": - main() diff --git a/wasm-mappings/source-map-mappings-wasm-api/src/lib.rs b/wasm-mappings/source-map-mappings-wasm-api/src/lib.rs index d91ed381..c669a5f2 100644 --- a/wasm-mappings/source-map-mappings-wasm-api/src/lib.rs +++ b/wasm-mappings/source-map-mappings-wasm-api/src/lib.rs @@ -1,54 +1,43 @@ //! The public JS API to the `source-map-mappings` crate. //! -//! ## Usage +//! This is a `wasm-bindgen` module. JS obtains a `Mappings` handle from +//! `parseMappings`, queries it, and calls `free()` on it when finished. //! -//! 1. Instantiate the WebAssembly module, supplying a JS implementation of -//! `mapping_callback`. -//! -//! 2. Allocate space for the mappings string with `allocate_mappings`. -//! -//! 3. Initialize the mappings string by copying the JS `String`'s data into it. -//! -//! 4. Parse the mappings with `parse_mappings`. Handle errors, if any. -//! -//! 5. Query the resulting `Mappings` structure as needed with -//! `by_generated_location`, `by_original_location`, `compute_column_spans`, -//! `original_location_for`, `generated_location_for`, and -//! `all_generated_locations_for` as needed. -//! -//! 6. When finished with `Mappings` structure, dispose of it with -//! `free_mappings`. - -// NB: every exported function must be `#[no_mangle]` and `pub extern "C"`. +//! Query methods that yield mappings take a `MappingCallback` -- a JS function +//! invoked once per mapping with a flat list of primitives. Passing the +//! callback per-call (rather than as a single module-level import) is what +//! lets JS avoid maintaining a stack of "currently active" callbacks. #![deny(missing_docs)] extern crate source_map_mappings; +extern crate wasm_bindgen; -use source_map_mappings::{Bias, Error, Mapping, Mappings}; -use std::mem; -use std::ptr; +use source_map_mappings::{Bias, Mapping}; use std::process; -use std::slice; +use wasm_bindgen::prelude::*; #[cfg(feature = "profiling")] mod observer { - use source_map_mappings; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn time(label: &str); + #[wasm_bindgen(js_namespace = console, js_name = timeEnd)] + fn time_end(label: &str); + } macro_rules! define_raii_observer { - ( $name:ident , $ctor:ident , $dtor:ident ) => { + ( $name:ident , $label:expr ) => { #[derive(Debug)] pub struct $name; impl Default for $name { #[inline] fn default() -> $name { - extern "C" { - fn $ctor(); - } - unsafe { - $ctor(); - } + time($label); $name } } @@ -56,48 +45,19 @@ mod observer { impl Drop for $name { #[inline] fn drop(&mut self) { - extern "C" { - fn $dtor(); - } - unsafe { - $dtor(); - } + time_end($label); } } - } + }; } - define_raii_observer!(ParseMappings, start_parse_mappings, end_parse_mappings); - define_raii_observer!( - SortByOriginalLocation, - start_sort_by_original_location, - end_sort_by_original_location - ); - define_raii_observer!( - SortByGeneratedLocation, - start_sort_by_generated_location, - end_sort_by_generated_location - ); - define_raii_observer!( - ComputeColumnSpans, - start_compute_column_spans, - end_compute_column_spans - ); - define_raii_observer!( - OriginalLocationFor, - start_original_location_for, - end_original_location_for - ); - define_raii_observer!( - GeneratedLocationFor, - start_generated_location_for, - end_generated_location_for - ); - define_raii_observer!( - AllGeneratedLocationsFor, - start_all_generated_locations_for, - end_all_generated_locations_for - ); + define_raii_observer!(ParseMappings, "parse_mappings"); + define_raii_observer!(SortByOriginalLocation, "sort_by_original_location"); + define_raii_observer!(SortByGeneratedLocation, "sort_by_generated_location"); + define_raii_observer!(ComputeColumnSpans, "compute_column_spans"); + define_raii_observer!(OriginalLocationFor, "original_location_for"); + define_raii_observer!(GeneratedLocationFor, "generated_location_for"); + define_raii_observer!(AllGeneratedLocationsFor, "all_generated_locations_for"); #[derive(Debug, Default)] pub struct Observer; @@ -115,201 +75,71 @@ mod observer { #[cfg(not(feature = "profiling"))] mod observer { + /// No-op observer used when the `profiling` feature is disabled. pub type Observer = (); } use observer::Observer; -static mut LAST_ERROR: Option = None; - -/// Get the last error's error code, or 0 if there was none. -/// -/// See `source_map_mappings::Error` for the error code definitions. -#[no_mangle] -pub extern "C" fn get_last_error() -> u32 { - unsafe { - match LAST_ERROR { - None => 0, - Some(e) => e as u32, - } - } -} - -#[inline] -fn assert_pointer_is_word_aligned(p: *mut u8) { - debug_assert_eq!(p as usize & (mem::size_of::() - 1), 0); -} - -/// Allocate space for a mappings string of the given size (in bytes). -/// -/// It is the JS callers responsibility to initialize the resulting buffer by -/// copying the JS `String` holding the source map's "mappings" into it (encoded -/// in ascii). -#[no_mangle] -pub extern "C" fn allocate_mappings(size: usize) -> *mut u8 { - // Make sure that we don't lose any bytes from size in the remainder. - let size_in_units_of_usize = (size + mem::size_of::() - 1) / mem::size_of::(); - - // Make room for two additional `usize`s: we'll stuff capacity and length in - // there. - let mut vec: Vec = Vec::with_capacity(size_in_units_of_usize + 2); - - // And do the stuffing. - let capacity = vec.capacity(); - vec.push(capacity); - vec.push(size); - - // Leak the vec's elements and get a pointer to them. - let ptr = vec.as_mut_ptr(); - debug_assert!(!ptr.is_null()); - mem::forget(vec); - - // Advance the pointer past our stuffed data and return it to JS, so that JS - // can write the mappings string into it. - let ptr = ptr.wrapping_offset(2) as *mut u8; - assert_pointer_is_word_aligned(ptr); - ptr -} - -#[inline] -fn constrain<'a, T>(_scope: &'a (), reference: &'a T) -> &'a T -where - T: ?Sized, -{ - reference -} - -/// Parse the given initialized mappings string into a `Mappings` structure. -/// -/// Returns `NULL` on failure, or a pointer to the parsed `Mappings` structure -/// on success. -/// -/// In the case of failure, the error can be retrieved with `get_last_error`. -/// -/// In the case of success, the caller takes ownership of the result, and must -/// call `free_mappings` to destroy it when finished. -/// -/// In both the success or failure cases, the caller gives up ownership of the -/// input mappings string and must not use it again. -#[no_mangle] -pub extern "C" fn parse_mappings(mappings: *mut u8) -> *mut Mappings { - assert_pointer_is_word_aligned(mappings); - let mappings = mappings as *mut usize; - - // Unstuff the data we put just before the pointer to the mappings - // string. - let capacity_ptr = mappings.wrapping_offset(-2); - debug_assert!(!capacity_ptr.is_null()); - let capacity = unsafe { *capacity_ptr }; - - let size_ptr = mappings.wrapping_offset(-1); - debug_assert!(!size_ptr.is_null()); - let size = unsafe { *size_ptr }; - - // Construct the input slice from the pointer and parse the mappings. - let result = unsafe { - let input = slice::from_raw_parts(mappings as *const u8, size); - let this_scope = (); - let input = constrain(&this_scope, input); - source_map_mappings::parse_mappings(input) - }; - - // Deallocate the mappings string and its two prefix words. - let size_in_usizes = (size + mem::size_of::() - 1) / mem::size_of::(); - unsafe { - Vec::::from_raw_parts(capacity_ptr, size_in_usizes + 2, capacity); - } - - // Return the result, saving any errors on the side for later inspection by - // JS if required. - match result { - Ok(mappings) => Box::into_raw(Box::new(mappings)), - Err(e) => { - unsafe { - LAST_ERROR = Some(e); - } - ptr::null_mut() - } - } -} - -/// Destroy the given `Mappings` structure. -/// -/// The caller gives up ownership of the mappings and must not use them again. -#[no_mangle] -pub extern "C" fn free_mappings(mappings: *mut Mappings) { - unsafe { - Box::from_raw(mappings); - } -} - -#[inline] -unsafe fn mappings_mut<'a>( - _scope: &'a (), - mappings: *mut Mappings, -) -> &'a mut Mappings { - mappings.as_mut().unwrap() -} - +#[wasm_bindgen] extern "C" { - fn mapping_callback( - // These two parameters are always valid. + /// A JS function invoked once per mapping. + pub type MappingCallback; + + /// Invoke the callback with a single mapping. + /// + /// `last_generated_column` is only meaningful when + /// `has_last_generated_column`; `source`, `original_line` and + /// `original_column` only when `has_original`; `name` only when + /// `has_name`. + #[wasm_bindgen(method, js_name = call, catch)] + fn invoke( + this: &MappingCallback, + ctx: &JsValue, generated_line: u32, generated_column: u32, - - // The `last_generated_column` parameter is only valid if - // `has_last_generated_column` is `true`. has_last_generated_column: bool, last_generated_column: u32, - - // The `source`, `original_line`, and `original_column` parameters are - // only valid if `has_original` is `true`. has_original: bool, source: u32, original_line: u32, original_column: u32, - - // The `name` parameter is only valid if `has_name` is `true`. has_name: bool, name: u32, - ); + ) -> Result<(), JsValue>; } #[inline] -unsafe fn invoke_mapping_callback(mapping: &Mapping) { - let generated_line = mapping.generated_line; - let generated_column = mapping.generated_column; - +fn emit(cb: &MappingCallback, mapping: &Mapping) -> Result<(), JsValue> { let (has_last_generated_column, last_generated_column) = - if let Some(last_generated_column) = mapping.last_generated_column { - (true, last_generated_column) - } else { - (false, 0) + match mapping.last_generated_column { + Some(c) => (true, c), + None => (false, 0), }; let (has_original, source, original_line, original_column, has_name, name) = - if let Some(original) = mapping.original.as_ref() { - let (has_name, name) = if let Some(name) = original.name { - (true, name) - } else { - (false, 0) - }; - - ( - true, - original.source, - original.original_line, - original.original_column, - has_name, - name, - ) - } else { - (false, 0, 0, 0, false, 0) + match mapping.original.as_ref() { + Some(original) => { + let (has_name, name) = match original.name { + Some(name) => (true, name), + None => (false, 0), + }; + ( + true, + original.source, + original.original_line, + original.original_column, + has_name, + name, + ) + } + None => (false, 0, 0, 0, false, 0), }; - mapping_callback( - generated_line, - generated_column, + cb.invoke( + &JsValue::NULL, + mapping.generated_line, + mapping.generated_column, has_last_generated_column, last_generated_column, has_original, @@ -318,44 +148,7 @@ unsafe fn invoke_mapping_callback(mapping: &Mapping) { original_column, has_name, name, - ); -} - -/// Invoke the `mapping_callback` on each mapping in the given `Mappings` -/// structure, in order of generated location. -#[no_mangle] -pub extern "C" fn by_generated_location(mappings: *mut Mappings) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; - - mappings - .by_generated_location() - .iter() - .for_each(|m| unsafe { - invoke_mapping_callback(m); - }); -} - -/// Compute column spans for the given mappings. -#[no_mangle] -pub extern "C" fn compute_column_spans(mappings: *mut Mappings) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; - - mappings.compute_column_spans(); -} - -/// Invoke the `mapping_callback` on each mapping in the given `Mappings` -/// structure that has original location information, in order of original -/// location. -#[no_mangle] -pub extern "C" fn by_original_location(mappings: *mut Mappings) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; - - mappings.by_original_location().for_each(|m| unsafe { - invoke_mapping_callback(m); - }); + ) } #[inline] @@ -363,94 +156,138 @@ fn u32_to_bias(bias: u32) -> Bias { match bias { 1 => Bias::GreatestLowerBound, 2 => Bias::LeastUpperBound, - otherwise => if cfg!(debug_assertions) { - panic!( - "Invalid `Bias = {}`; must be `Bias::GreatestLowerBound = {}` or \ - `Bias::LeastUpperBound = {}`", - otherwise, - Bias::GreatestLowerBound as u32, - Bias::LeastUpperBound as u32, - ) - } else { - process::abort() - }, + otherwise => { + if cfg!(debug_assertions) { + panic!( + "Invalid `Bias = {}`; must be `Bias::GreatestLowerBound = {}` or \ + `Bias::LeastUpperBound = {}`", + otherwise, + Bias::GreatestLowerBound as u32, + Bias::LeastUpperBound as u32, + ) + } else { + process::abort() + } + } } } -/// Find the mapping for the given generated location, if any exists. +/// A parsed `mappings` string, ready to be queried. /// -/// If a mapping is found, the `mapping_callback` is invoked with it -/// once. Otherwise, the `mapping_callback` is not invoked at all. -#[no_mangle] -pub extern "C" fn original_location_for( - mappings: *mut Mappings, - generated_line: u32, - generated_column: u32, - bias: u32, -) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; - let bias = u32_to_bias(bias); +/// JS owns this handle and must call `free()` on it when finished. +#[wasm_bindgen] +#[derive(Debug)] +pub struct Mappings { + inner: source_map_mappings::Mappings, +} + +/// Parse a source map's `mappings` string. +/// +/// On failure, rejects with the numeric `source_map_mappings::Error` code. +/// Replaces the old `allocate_mappings` / `parse_mappings` / `get_last_error` +/// trio: `wasm-bindgen` copies the string into wasm memory itself, so JS no +/// longer has to hand-manage a buffer. +#[wasm_bindgen(js_name = parseMappings)] +pub fn parse_mappings(mappings: &str) -> Result { + source_map_mappings::parse_mappings(mappings.as_bytes()) + .map(|inner| Mappings { inner }) + .map_err(|e| JsValue::from(e as u32)) +} - if let Some(m) = mappings.original_location_for(generated_line, generated_column, bias) { - unsafe { - invoke_mapping_callback(m); +#[wasm_bindgen] +impl Mappings { + /// Invoke `cb` on each mapping, in order of generated location. + #[wasm_bindgen(js_name = byGeneratedLocation)] + pub fn by_generated_location(&mut self, cb: &MappingCallback) -> Result<(), JsValue> { + for m in self.inner.by_generated_location().iter() { + emit(cb, m)?; } + Ok(()) } -} -/// Find the mapping for the given original location, if any exists. -/// -/// If a mapping is found, the `mapping_callback` is invoked with it -/// once. Otherwise, the `mapping_callback` is not invoked at all. -#[no_mangle] -pub extern "C" fn generated_location_for( - mappings: *mut Mappings, - source: u32, - original_line: u32, - original_column: u32, - bias: u32, -) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; - let bias = u32_to_bias(bias); + /// Invoke `cb` on each mapping that has original location information, in + /// order of original location. + #[wasm_bindgen(js_name = byOriginalLocation)] + pub fn by_original_location(&mut self, cb: &MappingCallback) -> Result<(), JsValue> { + for m in self.inner.by_original_location() { + emit(cb, m)?; + } + Ok(()) + } + + /// Compute column spans for the mappings. + #[wasm_bindgen(js_name = computeColumnSpans)] + pub fn compute_column_spans(&mut self) { + self.inner.compute_column_spans(); + } - if let Some(m) = mappings.generated_location_for(source, original_line, original_column, bias) { - unsafe { - invoke_mapping_callback(m); + /// Find the mapping for the given generated location, if any exists, and + /// invoke `cb` with it once. + #[wasm_bindgen(js_name = originalLocationFor)] + pub fn original_location_for( + &mut self, + generated_line: u32, + generated_column: u32, + bias: u32, + cb: &MappingCallback, + ) -> Result<(), JsValue> { + if let Some(m) = + self.inner + .original_location_for(generated_line, generated_column, u32_to_bias(bias)) + { + emit(cb, m)?; } + Ok(()) } -} -/// Find all mappings for the given original location, and invoke the -/// `mapping_callback` on each of them. -/// -/// If `has_original_column` is `true`, then the `mapping_callback` is only -/// invoked with mappings with matching source and original line **and** -/// original column is equal to `original_column`. If `has_original_column` is -/// `false`, then the `original_column` argument is ignored, and the -/// `mapping_callback` is invoked on all mappings with matching source and -/// original line. -#[no_mangle] -pub extern "C" fn all_generated_locations_for( - mappings: *mut Mappings, - source: u32, - original_line: u32, - has_original_column: bool, - original_column: u32, -) { - let this_scope = (); - let mappings = unsafe { mappings_mut(&this_scope, mappings) }; + /// Find the mapping for the given original location, if any exists, and + /// invoke `cb` with it once. + #[wasm_bindgen(js_name = generatedLocationFor)] + pub fn generated_location_for( + &mut self, + source: u32, + original_line: u32, + original_column: u32, + bias: u32, + cb: &MappingCallback, + ) -> Result<(), JsValue> { + if let Some(m) = self.inner.generated_location_for( + source, + original_line, + original_column, + u32_to_bias(bias), + ) { + emit(cb, m)?; + } + Ok(()) + } - let original_column = if has_original_column { - Some(original_column) - } else { - None - }; + /// Find all mappings for the given original location and invoke `cb` on + /// each of them. + /// + /// When `has_original_column` is false, `original_column` is ignored and + /// every mapping with a matching source and original line is yielded. + #[wasm_bindgen(js_name = allGeneratedLocationsFor)] + pub fn all_generated_locations_for( + &mut self, + source: u32, + original_line: u32, + has_original_column: bool, + original_column: u32, + cb: &MappingCallback, + ) -> Result<(), JsValue> { + let original_column = if has_original_column { + Some(original_column) + } else { + None + }; - for m in mappings.all_generated_locations_for(source, original_line, original_column) { - unsafe { - invoke_mapping_callback(m); + for m in self + .inner + .all_generated_locations_for(source, original_line, original_column) + { + emit(cb, m)?; } + Ok(()) } } diff --git a/wasm-mappings/source-map-mappings-wasm-api/who-calls.py b/wasm-mappings/source-map-mappings-wasm-api/who-calls.py deleted file mode 100755 index aa908427..00000000 --- a/wasm-mappings/source-map-mappings-wasm-api/who-calls.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import subprocess - -DESC = """ - -List the callers of some function in the given `.wasm` file. - -Constructs the call graph of functions in the `.wasm` file and then queries -edges in that call graph. - -Requires that the `wasm-objdump` tool from the WABT[0] is installed and on the -`$PATH`. - -[0]: https://github.com/WebAssembly/wabt - -## Example Usage - -Print every caller of `std::panicking::begin_panic`: - - $ ./who-calls.py path/to/something.wasm \\ - --function "std::panicking::begin_panic::h59c9bbae5c8cc295" - -Print the top 5 largest functions and their callers: - - $ ./who-calls.py path/to/something.wasm --top - -""" - -parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - description=DESC) - -parser.add_argument( - "wasm_file", - type=str, - help="The input `.wasm` file.") - -parser.add_argument( - "--function", - type=str, - help="The function whose callers should be listed.") - -parser.add_argument( - "-t", - "--top", - type=int, - default=None, - help="Display the largest N functions and their callers") - -parser.add_argument( - "-d", - "--max-depth", - type=int, - default=None, - help="The maximum call stack depth to display") - -def decode(f): - return f.decode(encoding="utf-8", errors="ignore") - -def run(cmd, **kwargs): - kwargs["stdout"] = subprocess.PIPE - child = subprocess.run(cmd, **kwargs) - if child.returncode != 0: - raise Exception("{} did not exit OK".format(str(cmd))) - return decode(child.stdout) - -def disassemble(args): - return run(["wasm-objdump", "-d", args.wasm_file]) - -START_FUNCTION = re.compile(r"^(\w+) <([\w<>:\s]+)>:$") -CALL_FUNCTION = re.compile(r"^ \w+: [\w ]*\|\s*call \w+ <([\w<>:\s]+)>$") - -def parse_call_graph(disassembly, args): - call_graph = {} - current_function = None - - for line in disassembly.splitlines(): - match = re.match(START_FUNCTION, line) - if match: - current_function = match.groups()[1] - call_graph[current_function] = set() - continue - - match = re.match(CALL_FUNCTION, line) - if match and current_function: - call_graph[current_function].add(match.groups()[0]) - - return call_graph - -def parse_top_functions(disassembly, args): - functions = [] - last_function = None - - for line in disassembly.splitlines(): - match = re.match(START_FUNCTION, line) - if match: - (start, function) = match.groups() - start = int(start, 16) - if last_function: - (f, last_start) = last_function - functions.append((f, start - last_start)) - last_function = (function, start) - - top_functions = sorted(functions, key=lambda a: a[1], reverse=True) - return top_functions[:args.top] - -def reverse_call_graph(call_graph, args): - reversed_call_graph = {} - - for function, calls in call_graph.items(): - if function not in reversed_call_graph: - reversed_call_graph[function] = set() - - for call in calls: - if call not in reversed_call_graph: - reversed_call_graph[call] = set() - reversed_call_graph[call].add(function) - - return reversed_call_graph - -def print_callers(reversed_call_graph, args, function=None, depth=0, seen=set()): - if not function: - function = args.function - seen.add(function) - - if depth == 0: - depth += 1 - print("{}".format(function)) - if function not in reversed_call_graph: - print(" ") - return - - if args.max_depth is None or depth < args.max_depth: - for caller in reversed_call_graph[function]: - if caller in seen: - continue - - indent = "" - for _ in range(0, depth): - indent += " " - - print("{}⬑ {}".format(indent, caller)) - - print_callers(reversed_call_graph, args, function=caller, depth=depth+1, seen=seen) - - seen.remove(function) - -def main(): - args = parser.parse_args() - disassembly = disassemble(args) - call_graph = parse_call_graph(disassembly, args) - reversed_call_graph = reverse_call_graph(call_graph, args) - - if args.function: - print_callers(reversed_call_graph, args) - elif args.top: - top_functions = parse_top_functions(disassembly, args) - for f, size in top_functions: - print(size, "bytes: ", end="") - print_callers(reversed_call_graph, args, function=f) - else: - raise Exception("Must use one of --function or --top") - -if __name__ == "__main__": - main() diff --git a/wasm-mappings/src/comparators.rs b/wasm-mappings/src/comparators.rs index 23f78878..0d2d051f 100644 --- a/wasm-mappings/src/comparators.rs +++ b/wasm-mappings/src/comparators.rs @@ -7,7 +7,7 @@ use std::fmt; /// A function that can compare two `T`s. pub trait ComparatorFunction: fmt::Debug { /// Compare the given values. - fn compare(&T, &T) -> Ordering; + fn compare(a: &T, b: &T) -> Ordering; } impl ComparatorFunction> for F diff --git a/wasm-mappings/src/lib.rs b/wasm-mappings/src/lib.rs index e93736f6..fd7482db 100755 --- a/wasm-mappings/src/lib.rs +++ b/wasm-mappings/src/lib.rs @@ -1,6 +1,5 @@ #![deny(missing_debug_implementations)] -extern crate rand; extern crate vlq; pub mod comparators; @@ -10,7 +9,6 @@ use std::cmp; use std::marker::PhantomData; use std::mem; use std::slice; -use std::u32; /// Errors that can occur during parsing. #[derive(Copy, Clone, Debug)] @@ -131,7 +129,7 @@ where items } }; - mem::replace(self, LazilySorted::Sorted(items, PhantomData, PhantomData)); + *self = LazilySorted::Sorted(items, PhantomData, PhantomData); unwrap(self.sorted()) } @@ -170,7 +168,7 @@ where { by_generated: Vec, computed_column_spans: bool, - observer: O, + observer: PhantomData, // The `by_original` field maps source index to mappings within that // original source. This lets us essentially do bucket sort on a per-source @@ -274,7 +272,7 @@ impl Mappings { /// Iterate over all mappings that contain original location information, /// sorted by their original location information. #[inline] - pub fn by_original_location(&mut self) -> ByOriginalLocation { + pub fn by_original_location(&mut self) -> ByOriginalLocation<'_, O::SortByOriginalLocation> { ByOriginalLocation { buckets: self.source_buckets().iter_mut(), this_bucket: [].iter(), @@ -389,7 +387,7 @@ impl Mappings { source: u32, original_line: u32, original_column: Option, - ) -> AllGeneratedLocationsFor { + ) -> AllGeneratedLocationsFor<'_> { let _observer = O::AllGeneratedLocationsFor::default(); let query_column = original_column.unwrap_or(0); @@ -452,7 +450,7 @@ impl Default for Mappings { by_generated: vec![], by_original: None, computed_column_spans: false, - observer: Default::default(), + observer: PhantomData, } } } diff --git a/wasm-mappings/tests/quickcheck.rs b/wasm-mappings/tests/quickcheck.rs index 08f03a4f..96996e8e 100644 --- a/wasm-mappings/tests/quickcheck.rs +++ b/wasm-mappings/tests/quickcheck.rs @@ -7,7 +7,6 @@ use quickcheck::{Arbitrary, Gen}; use source_map_mappings::{Bias, Error}; use std::cmp::Ordering; use std::fmt; -use std::i64; use std::iter; use std::marker::PhantomData; @@ -23,11 +22,14 @@ impl Arbitrary for Vlq where R: VlqRange, { - fn arbitrary(g: &mut G) -> Self { - Vlq(g.gen_range(R::low(), R::high()), PhantomData) + fn arbitrary(g: &mut Gen) -> Self { + // Uniform-ish over [low, high), like the old `Gen::gen_range`. + let span = (R::high() as i128) - (R::low() as i128); + let offset = (u64::arbitrary(g) as i128) % span; + Vlq((R::low() as i128 + offset) as i64, PhantomData) } - fn shrink(&self) -> Box> { + fn shrink(&self) -> Box> { Box::new(self.0.shrink().map(|x| Vlq(x, PhantomData))) } } @@ -64,8 +66,8 @@ impl Arbitrary for Mapping where R: VlqRange, { - fn arbitrary(g: &mut G) -> Self { - match g.gen_range(0, 3) { + fn arbitrary(g: &mut Gen) -> Self { + match *g.choose(&[0, 1, 2]).unwrap() { 0 => Mapping::Generated { generated_column: Vlq::::arbitrary(g), }, @@ -86,7 +88,7 @@ where } } - fn shrink(&self) -> Box> { + fn shrink(&self) -> Box> { match *self { Mapping::Generated { generated_column } => Box::new( generated_column @@ -203,11 +205,11 @@ impl Arbitrary for GeneratedLine where R: VlqRange, { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { GeneratedLine(Vec::arbitrary(g)) } - fn shrink(&self) -> Box> { + fn shrink(&self) -> Box> { Box::new(self.0.shrink().map(|v| GeneratedLine(v))) } } @@ -233,11 +235,11 @@ impl Arbitrary for Mappings where R: VlqRange, { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { Mappings(Vec::arbitrary(g)) } - fn shrink(&self) -> Box> { + fn shrink(&self) -> Box> { Box::new(self.0.shrink().map(|v| Mappings(v))) } }