diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..17de1688 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# The header of Codeowners is autogenerated from https://github.com/integromat/mono. Don’t edit code before END-AUTOGENERATED-SECTION! +* @integromat/build-run-backend +### END-AUTOGENERATED-SECTION diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml similarity index 68% rename from .github/workflows/build.yml rename to .github/workflows/ci.yml index 8233b9af..181c6e8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,13 @@ name: Build & Test -on: [push, pull_request] +on: + pull_request: + paths: + - '.github/workflows/ci.yml' + - 'src/**' + - 'vendor/**' + - 'tests/**' + - '*.js' jobs: build-and-test: @@ -8,28 +15,22 @@ jobs: strategy: matrix: node-version: [ 16.x, 18.x, 20.x ] - os: [ ubuntu-latest, macos-latest, windows-latest ] + os: [ ubuntu-latest, macos-latest, windows-latest, make-ubuntu24-arm64-2c ] fail-fast: false steps: - uses: actions/checkout@v4 - # https://github.com/nodejs/node-gyp/issues/2869 - - run: python3 -m pip install setuptools - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Add msbuild to PATH if: matrix.os == 'windows-latest' - uses: microsoft/setup-msbuild@v1.3 - - name: Install node-gyp - if: matrix.os == 'windows-latest' - run: | - npm install --global node-gyp@latest + uses: microsoft/setup-msbuild@v2 - name: Build run: | - npm install --build-from-source + npm install && npm run rebuild - name: Test - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: max_attempts: 3 retry_on: error @@ -50,7 +51,7 @@ jobs: apk add g++ make python3 - name: Build run: | - npm install --build-from-source + npm install && npm run rebuild - name: Test run: | npm test diff --git a/.github/workflows/prebuild.yml b/.github/workflows/prebuild.yml deleted file mode 100644 index 6a386fde..00000000 --- a/.github/workflows/prebuild.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Add native binaries to release - -on: - push: - tags: ['*'] - workflow_dispatch: - -jobs: - prebuild: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-20.04, macos-latest, windows-latest] - fail-fast: false - steps: - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - name: Add msbuild to PATH - if: matrix.os == 'windows-latest' - uses: microsoft/setup-msbuild@v1.3 - - name: Install node-gyp - if: matrix.os == 'windows-latest' - run: | - npm install --global node-gyp@latest - - name: Build - run: | - npm install --ignore-scripts - - name: Prebuild - run: | - npm run prebuild - - name: Upload - run: | - npx prebuild --upload ${{ secrets.UPLOAD_TOKEN }} - env: - MAKEFLAGS: -j4 - - prebuild-alpine: - runs-on: ubuntu-latest - container: node:18-alpine3.19 - steps: - - uses: actions/checkout@v4 - - name: install build deps - run: | - apk add g++ make python3 - - name: Build - run: | - npm install --ignore-scripts - - name: Prebuild - run: | - npm run prebuild - - name: Upload - run: | - npx prebuild --upload ${{ secrets.UPLOAD_TOKEN }} - env: - MAKEFLAGS: -j4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..7950c8bb --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,102 @@ +name: Publish to NPM with pre-built binaries + +on: + release: + types: [created] + +jobs: + build-linux: + runs-on: ${{ matrix.os }} + # This will force build on an older libc version + container: node:20-bullseye + strategy: + matrix: + os: [ ubuntu-latest, make-ubuntu24-arm64-2c ] + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + cache-dependency-path: 'package-lock.json' + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Build native extension + run: npx prebuildify --napi=false --name isolated-vm --tag-libc -t v16.20.2 -t v18.20.4 -t v20.16.0 --strip + env: + npm_config_jobs: max # max parallel node-gyp compile jobs + + - name: Archive production artifacts + uses: actions/upload-artifact@v3 + with: + name: binaries-${{ matrix.os }} + path: ./prebuilds + + build-others: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + cache-dependency-path: 'package-lock.json' + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Add msbuild to PATH + if: matrix.os == 'windows-latest' + uses: microsoft/setup-msbuild@v2 + + - name: Build native extension + run: npx prebuildify --napi=false --name isolated-vm -t v16.20.2 -t v18.20.4 -t v20.16.0 --strip + env: + npm_config_jobs: max # max parallel node-gyp compile jobs + + - name: Archive production artifacts + uses: actions/upload-artifact@v3 + with: + name: binaries-${{ matrix.os }} + path: ./prebuilds + + publish: + needs: + - build-others + - build-linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + cache-dependency-path: 'package-lock.json' + registry-url: 'https://registry.npmjs.org' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: /tmp/artifacts + + - name: Move artifacts to prebuilds directory + run: mkdir --parents ./prebuilds && cp --recursive /tmp/artifacts/binaries-*/* ./prebuilds + + - name: Publish to NPM + run: npm publish diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml new file mode 100644 index 00000000..27402b10 --- /dev/null +++ b/.github/workflows/validate-pr.yml @@ -0,0 +1,50 @@ +###### +# This file is autogenerated from https://github.com/integromat/mono/blob/master/libs/github-resources/src/repositories/isolated-vm.ts. Do not edit manually!!! +###### + +name: PR +on: + pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled + - edited + merge_group: + +# run only latest workflow, cancel other runs of this workflow +concurrency: + group: validate-pr-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: '1' + +jobs: + validate-pr: + name: Validate PR + runs-on: ubuntu-latest + permissions: + id-token: write + issues: write + pull-requests: write + contents: read + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + if: github.event_name != 'merge_group' + + - name: Setup Node.js + if: github.event_name != 'merge_group' + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + with: + registry-url: https://registry.npmjs.org + node-version: '>=20.0' + + - name: Validate PR + if: github.event_name != 'merge_group' + run: npx @integromat/actions-toolkit validate-pr --skipPrDescriptionLengthValidation diff --git a/.gitignore b/.gitignore index ad6e3721..63e41f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ build/ node_modules/ -package-lock.json out/ .cache/ compile_commands.json diff --git a/.npmignore b/.npmignore index 324e90b6..e5cf696d 100644 --- a/.npmignore +++ b/.npmignore @@ -2,9 +2,9 @@ test.js tests out/ build/ -prebuild/ .github/ .prebuildrc .travis.yml +.clang-tidy .vscode/ *.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..74a76c9f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,121 @@ +# isolated-vm — Agent Context + +## What + +`@integromat/isolated-vm` is a Make-internal fork of the [`isolated-vm`](https://github.com/laverdet/isolated-vm) npm package. It is a native Node.js C++ addon that exposes V8's `Isolate` interface, enabling fully isolated JavaScript sandboxes with separate heaps, memory limits, and security boundaries. The fork is published as `@integromat/isolated-vm` and is currently at v4.8.3 (forked from upstream v4.3.0). + +**Tech stack:** C++17 native addon · Direct V8 API (not NAN/NAPI) · node-gyp build · prebuildify for binary distribution · Node.js >=16 <21. + +## Why + +The package lets Make run untrusted user code in secure, memory-limited V8 isolates completely separated from the host Node.js process. Each isolate has its own heap; data crossing isolate boundaries must be explicitly serialized or transferred. + +## Architecture + +### JS Layer + +`isolated-vm.js` is a single-line shim: `module.exports = require('node-gyp-build')(__dirname).ivm`. The entire public API (all classes and constructors) is native C++. TypeScript types are declared in `isolated-vm.d.ts` as `namespace IsolatedVM`. + +### C++ Source Layout + +- `src/module/` — JS-visible handle classes: `IsolateHandle`, `ContextHandle`, `ScriptHandle`, `ModuleHandle`, `ReferenceHandle`, `ExternalCopyHandle`, `Callback`, `NativeModuleHandle`, `LibraryHandle` +- `src/isolate/` — core runtime: `IsolateEnvironment`, `IsolateHolder`, `Executor`, `Scheduler` hierarchy, `ThreePhaseTask`, `ClassHandle`, `RemoteHandle` +- `src/external_copy/` — cross-isolate value serialization (`ExternalCopy` subclasses, structured clone) +- `src/lib/` — primitives: `thread_pool_t`, `timer`, `lockable_t`, `covariant_t` +- `src/isolated_vm.h` — narrow public C++ API for embedding (exposes `IsolateHolder`, `RemoteHandle`, `Runnable`) +- `vendor/v8_inspector/` — vendored Node.js-version-specific inspector headers for v16/v18/v20 + +### Binding Mechanism + +**Direct V8 API** (no NAN/NAPI). `ClassHandle` (`src/isolate/class_handle.h`) is the base for all JS-exposed objects: +- Creates `v8::FunctionTemplate` with `InternalFieldCount(1)` to store a raw native pointer +- `Wrap()` stores the `ClassHandle*` in `InternalField[0]` and sets a weak GC callback +- `Unwrap()` reads `InternalField[0]` and `dynamic_cast` +- All class prototypes and instances are frozen with `SetIntegrityLevel(kFrozen)` unless opted out + +`IsolateSpecific` (`src/isolate/specific.h`) is a per-isolate analogue of `thread_local` — stores data in `IsolateEnvironment::specifics` (a `std::vector>`) keyed by an atomically assigned global index. Used to cache `FunctionTemplate`s per-isolate. + +### Isolate Lifecycle + +`IsolateEnvironment` (`src/isolate/environment.h`) owns the raw `v8::Isolate*`, scheduler, executor, and memory tracking. Held via `IsolateHolder` (`src/isolate/holder.h`) which wraps it in a `lockable_t>`. + +Two factory paths: +- `IsolateEnvironment::New(v8::Isolate*, v8::Context)` — wraps the existing Node.js default isolate; uses `UvScheduler` +- `IsolateEnvironment::New(memory_limit_mb, snapshot, ...)` — creates a fresh V8 isolate with `LimitedAllocator`; uses `IsolatedScheduler` + +### Scheduler Architecture + +``` +Scheduler (base: mutex + 4 task queues: tasks, handle_tasks, interrupts, sync_interrupts) + └── LockedScheduler + ├── UvScheduler — default Node.js isolate, wakes via uv_async_send + └── IsolatedScheduler — user isolates, dispatches to thread_pool_t +``` + +`thread_pool_t` (`src/lib/thread_pool.h`) is fixed-size (`hardware_concurrency + 1`). Thread affinity (`affinity_t`) tracks which pool threads are associated with each isolate. + +`covariant_t` (`src/lib/covariant.h`) is a tagged-union/aligned-storage that holds one of several types sharing a base class — used so `IsolateEnvironment` stores the scheduler inline without heap allocation. + +### Three-Phase Task Protocol + +All cross-isolate operations use `ThreePhaseTask` (`src/isolate/three_phase_task.h`): + +| Phase | Runs in | Purpose | +|-------|---------|---------| +| Phase 1 (constructor) | Calling isolate | Serialize data out | +| Phase 2 (`Phase2()`) | Target isolate | Do work, serialize results | +| Phase 3 (`Phase3()`) | Calling isolate | Deserialize results, resolve promise | + +`ThreePhaseTask::Run()` dispatches on 4 modes: full async (returns promise), fire-and-forget (ignored), fully sync (acquires `Executor::Lock` directly), and sync+async (suspends caller thread while default isolate handles async work). + +### Thread Safety + +- `v8::Locker` — enforces single-threaded V8 isolate access. `Executor::Lock` RAII-wraps: `v8::Locker → v8::Isolate::Scope → v8::HandleScope`. +- `lockable_t` (`src/lib/lockable.h`) — bundles a resource with a mutex (and optionally a condition variable). Used for `IsolateHolder::isolate`, `AsyncWait::state`, and the global `default_isolates` map. +- `Executor` (`src/isolate/executor.h`) tracks the current environment via `thread_local Executor* current_executor`. `Executor::Scope` sets this on entry. +- `Scheduler::AsyncWait` — parks the calling thread while waiting for cross-isolate async results; uses a waitable `lockable_t`. + +### Method Naming Convention + +Every native operation exposes up to three variants: +- `method()` — async, returns `Promise` +- `methodSync()` — synchronous, blocks calling thread +- `methodIgnored()` — fire-and-forget, returns `undefined`, swallows exceptions + +`applySyncPromise` is special: only callable on default-isolate functions from a non-default thread; the calling isolate suspends while the promise resolves on the default thread. + +### ExternalCopy / Serialization + +`ExternalCopy` subclasses (`src/external_copy/`) serialize V8 values out of one isolate for materialization in another. Key subclasses: `ExternalCopyArrayBuffer` (detaches backing store), `ExternalCopySharedArrayBuffer` (shares backing store), `ExternalCopyError`. `CopyIntoCheckHeap()` calls `IsolateEnvironment::HeapCheck` before materializing to enforce memory limits. Global external size tracked atomically via `TotalExternalSize()`. + +The `nortti` static library target in `binding.gyp` compiles `serializer_nortti.cc` and `allocator_nortti.cc` without RTTI to avoid ODR conflicts with V8's own RTTI-less objects. + +## Build + +```sh +npm run rebuild # full native build (node-gyp rebuild --release -j max) +npm run prebuild # prebuildify (generates platform-specific binaries) +``` + +**`binding.gyp`** defines 3 targets: `isolated_vm` (the `.node` addon, C++17, exceptions+RTTI enabled), `nortti` (static lib without RTTI, non-Windows only), and `action_after_build` (copies `.node` to `out/`). + +## Testing + +```sh +node test.js +``` + +`test.js` spawns each `tests/*.js` file as a subprocess (always with `--no-node-snapshot`). A test passes iff: stdout == `'pass\n'`, stderr is empty, exit code is 0. Tests signal pass via `console.log('pass')`. Assertions use Node's built-in `assert`. To add node flags, place `// node-args: --flag` on line 1 of the test file. + +## Make Fork Specifics + +- Package name `@integromat/isolated-vm`; published to npm via `secrets.NPM_TOKEN` +- CI matrix includes `make-ubuntu24-arm64-2c` (Make internal self-hosted ARM64 runner) +- `validate-pr.yml` and `CODEOWNERS` are autogenerated from `integromat/mono` repo; CODEOWNERS assigns `@integromat/build-run-backend` +- Key fork fix: exception propagation from `Phase2Runner` to calling isolate (`three_phase_task.cc`, commits #9/#10) +- `devDependencies` includes `"isolated-vm": "."` — tests `require('isolated-vm')` which resolves to the local package via `NODE_PATH=__dirname` + +## When in Plan Mode +- Make the plan extremely concise. Sacrifice grammar for the sake of concision. +- Interview user in detail (for Claude: use the AskUserQuestionTool) about literally anything: technical implementation, UI & UX, concerns, tradeoffs, etc. but make sure the questions are not obvious. Be very in-depth and continue interviewing the user continually until it's complete. Use the answers to create a detailed spec. +- Make assumptions explicit: When you must proceed under uncertainty, list assumptions up front and continue. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/isolated-vm.js b/isolated-vm.js index 45d055c2..f4c41c40 100644 --- a/isolated-vm.js +++ b/isolated-vm.js @@ -1 +1 @@ -module.exports = require('./out/isolated_vm').ivm; +module.exports = require('node-gyp-build')(__dirname).ivm; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..af82b796 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1512 @@ +{ + "name": "@integromat/isolated-vm", + "version": "4.8.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@integromat/isolated-vm", + "version": "4.8.3", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-gyp-build": "^4.8.1" + }, + "devDependencies": { + "isolated-vm": ".", + "node-gyp": "^10.1.0", + "prebuildify": "^6.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cacache": { + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", + "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/isolated-vm": { + "resolved": "", + "link": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.0.tgz", + "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/prebuildify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz", + "integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "mkdirp-classic": "^0.5.3", + "node-abi": "^3.3.0", + "npm-run-path": "^3.1.0", + "pump": "^3.0.0", + "tar-fs": "^2.1.0" + }, + "bin": { + "prebuildify": "bin.js" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 79f95f03..c1647d8c 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "isolated-vm", - "version": "4.7.2", - "description": "Access to multiple isolates", + "name": "@integromat/isolated-vm", + "version": "4.8.3", + "description": "Access to multiple isolates - Make Fork", "main": "isolated-vm.js", "types": "isolated-vm.d.ts", "engines": { - "node": ">=16.0.0" + "node": ">=16 <21" }, "scripts": { - "install": "prebuild-install || (node-gyp rebuild --release -j max && node-gyp clean)", + "install": "node-gyp-build-optional", "rebuild": "node-gyp rebuild --release -j max", "prebuild": "prebuild", "upload": "prebuild --upload ${GITHUB_TOKEN}", @@ -16,11 +16,12 @@ "test": "node test.js" }, "dependencies": { - "prebuild-install": "^7.1.1" + "node-gyp-build": "^4.8.1" }, "devDependencies": { "isolated-vm": ".", - "prebuild": "^13.0.0" + "node-gyp": "^10.1.0", + "prebuildify": "^6.0.1" }, "binary": { "module_path": "out" diff --git a/src/external_copy/external_copy.cc b/src/external_copy/external_copy.cc index bded7b6b..d333fc62 100644 --- a/src/external_copy/external_copy.cc +++ b/src/external_copy/external_copy.cc @@ -8,6 +8,7 @@ #include "isolate/functor_runners.h" #include "isolate/util.h" #include "isolate/v8_version.h" +#include "isolate/generic/object.h" #include #include @@ -113,6 +114,12 @@ auto ExternalCopy::operator= (ExternalCopy&& that) noexcept -> ExternalCopy& { auto ExternalCopy::Copy(Local value, bool transfer_out, ArrayRange transfer_list) -> std::unique_ptr { + if (value->IsProxy()) { + // We just unwrap target from the proxy object. This will allow us to transfer Proxies that act as a wrapper + // for more sophisticated proxies this will be incorrect. + value = value.As()->GetTarget(); + } + std::unique_ptr copy = CopyIfPrimitive(value); if (copy) { return copy; @@ -128,71 +135,33 @@ auto ExternalCopy::Copy(Local value, bool transfer_out, ArrayRange transf } } else if (value->IsSharedArrayBuffer()) { return std::make_unique(value.As()); - } else if (value->IsArrayBufferView()) { - Local view = value.As(); - using ViewType = ExternalCopyArrayBufferView::ViewType; - ViewType type; - if (view->IsUint8Array()) { - type = ViewType::Uint8; - } else if (view->IsUint8ClampedArray()) { - type = ViewType::Uint8Clamped; - } else if (view->IsInt8Array()) { - type = ViewType::Int8; - } else if (view->IsUint16Array()) { - type = ViewType::Uint16; - } else if (view->IsInt16Array()) { - type = ViewType::Int16; - } else if (view->IsUint32Array()) { - type = ViewType::Uint32; - } else if (view->IsInt32Array()) { - type = ViewType::Int32; - } else if (view->IsFloat32Array()) { - type = ViewType::Float32; - } else if (view->IsFloat64Array()) { - type = ViewType::Float64; - } else if (view->IsBigInt64Array()) { - type = ViewType::BigInt64; - } else if (view->IsBigUint64Array()) { - type = ViewType::BigUint64; - } else if (view->IsDataView()) { - type = ViewType::DataView; - } else { - assert(false); - } - - // Sometimes TypedArrays don't actually have a real buffer allocated for them. The call to - // `Buffer()` below will force v8 to attempt to create a buffer if it doesn't exist, and if - // there is an allocation failure it will crash the process. - if (!view->HasBuffer()) { - auto* allocator = IsolateEnvironment::GetCurrent().GetLimitedAllocator(); - if (allocator != nullptr && !allocator->Check(view->ByteLength())) { - throw RuntimeRangeError("Array buffer allocation failed"); + } else if (value->IsObject()) { + if (value->IsArrayBufferView()) { + Local view = value.As(); + + // Sometimes TypedArrays don't actually have a real buffer allocated for them. The call to + // `Buffer()` below will force v8 to attempt to create a buffer if it doesn't exist, and if + // there is an allocation failure it will crash the process. + if (!view->HasBuffer()) { + auto* allocator = IsolateEnvironment::GetCurrent().GetLimitedAllocator(); + if (allocator != nullptr && !allocator->Check(view->ByteLength())) { + throw RuntimeRangeError("Array buffer allocation failed"); + } + } + + auto buffer = view->Buffer(); + + if (buffer->IsArrayBuffer()) { + // If we are supposed to transfer the buffer out, and it's not in the list of buffers to transfer, + // we simply create a new list + if (transfer_out && std::find(transfer_list.begin(), transfer_list.end(), buffer) == transfer_list.end()) { + auto *isolate = IsolateEnvironment::GetCurrent().GetIsolate(); + auto buffer_value = buffer.As(); + transfer_list = {Array::New(isolate, &buffer_value, 1), isolate->GetCurrentContext()}; + } } } - // `Buffer()` returns a Local but it may be a Local - Local tmp = view->Buffer(); - if (tmp->IsArrayBuffer()) { - Local array_buffer = tmp.As(); - if (!transfer_out) { - transfer_out = std::find(transfer_list.begin(), transfer_list.end(), array_buffer) != transfer_list.end(); - } - // Grab byte_offset and byte_length before the transfer because "neutering" the array buffer will null these out - size_t byte_offset = view->ByteOffset(); - size_t byte_length = view->ByteLength(); - std::unique_ptr external_buffer; - if (transfer_out) { - external_buffer = ExternalCopyArrayBuffer::Transfer(array_buffer); - } else { - external_buffer = std::make_unique(array_buffer); - } - return std::make_unique(std::move(external_buffer), type, byte_offset, byte_length); - } else { - assert(tmp->IsSharedArrayBuffer()); - Local array_buffer = tmp.As(); - return std::make_unique(std::make_unique(array_buffer), type, view->ByteOffset(), view->ByteLength()); - } - } else if (value->IsObject()) { return std::make_unique(value.As(), transfer_list); } else { throw RuntimeTypeError("Unsupported type"); @@ -457,16 +426,17 @@ auto ExternalCopySharedArrayBuffer::CopyInto(bool /*transfer_in*/) -> Local buffer, - ViewType type, size_t byte_offset, size_t byte_length + ViewType type, size_t byte_offset, size_t byte_length, + bool is_node_buffer ) : ExternalCopy(sizeof(ExternalCopyArrayBufferView)), - buffer(std::move(buffer)), - type(type), - byte_offset(byte_offset), byte_length(byte_length) {} + is_node_buffer(is_node_buffer), + type(type), + byte_offset(byte_offset), + byte_length(byte_length) {} template -auto NewTypedArrayView(Local buffer, ExternalCopyArrayBufferView::ViewType type, size_t byte_offset, size_t byte_length) -> Local { +auto NewTypedArrayView(Local buffer, ExternalCopyArrayBufferView::ViewType type, size_t byte_offset, size_t byte_length) -> Local { switch (type) { case ExternalCopyArrayBufferView::ViewType::Uint8: return Uint8Array::New(buffer, byte_offset, byte_length >> 0); @@ -497,13 +467,84 @@ auto NewTypedArrayView(Local buffer, ExternalCopyArrayBufferView::ViewType ty } } -auto ExternalCopyArrayBufferView::CopyInto(bool transfer_in) -> Local { - Local buffer = this->buffer->CopyInto(transfer_in); - if (buffer->IsArrayBuffer()) { - return NewTypedArrayView(buffer.As(), type, byte_offset, byte_length); +auto ExternalCopyArrayBufferView::CopyInto(bool /*transfer_in*/) -> Local { + assert(!underlying_buffer.IsEmpty()); + Local view; + if (underlying_buffer->IsArrayBuffer()) { + view = NewTypedArrayView(underlying_buffer.As(), type, byte_offset, byte_length); } else { - return NewTypedArrayView(buffer.As(), type, byte_offset, byte_length); + view = NewTypedArrayView(underlying_buffer.As(), type, byte_offset, byte_length); } + + if (is_node_buffer && type == ViewType::Uint8) { + auto& environment = IsolateEnvironment::GetCurrent(); + auto buffer_prototype = environment.GetBufferPrototype(); + if (!buffer_prototype.IsEmpty() && buffer_prototype->IsObject()) { + view->SetPrototype(Isolate::GetCurrent()->GetCurrentContext(), buffer_prototype).Check(); + } + } + + if (!own_properties.IsEmpty()) { + Local context = Isolate::GetCurrent()->GetCurrentContext(); + CopyObjectProperties(context, view, own_properties); + } + + return view; +} + +auto ExternalCopyArrayBufferView::Copy(v8::Local view) -> std::unique_ptr { + ViewType type; + if (view->IsUint8Array()) { + type = ViewType::Uint8; + } else if (view->IsUint8ClampedArray()) { + type = ViewType::Uint8Clamped; + } else if (view->IsInt8Array()) { + type = ViewType::Int8; + } else if (view->IsUint16Array()) { + type = ViewType::Uint16; + } else if (view->IsInt16Array()) { + type = ViewType::Int16; + } else if (view->IsUint32Array()) { + type = ViewType::Uint32; + } else if (view->IsInt32Array()) { + type = ViewType::Int32; + } else if (view->IsFloat32Array()) { + type = ViewType::Float32; + } else if (view->IsFloat64Array()) { + type = ViewType::Float64; + } else if (view->IsBigInt64Array()) { + type = ViewType::BigInt64; + } else if (view->IsBigUint64Array()) { + type = ViewType::BigUint64; + } else if (view->IsDataView()) { + type = ViewType::DataView; + } else { + assert(false); + } + + auto& environment = IsolateEnvironment::GetCurrent(); + auto *isolate = Isolate::GetCurrent(); + Local context = isolate->GetCurrentContext(); + + auto buffer_prototype = environment.GetBufferPrototype(); + bool is_node_buffer = !buffer_prototype.IsEmpty() && view->GetPrototype()->Equals(context, buffer_prototype).ToChecked(); + size_t byte_offset = view->ByteOffset(); + size_t byte_length = view->ByteLength(); + + return std::make_unique(type, byte_offset, byte_length, is_node_buffer); +} + +auto ExternalCopyArrayBufferView::CopyOwnProperties(v8::Isolate* isolate, v8::Local view) -> v8::Local { + Local context = isolate->GetCurrentContext(); + Local properties = Object::New(isolate); + + auto property_names = GetObjectOwnProperties(context, view); + if (property_names->Length() > 0) { + CopyObjectProperties(context, properties, view, property_names); + return properties; + } + + return properties; } } // namespace ivm diff --git a/src/external_copy/external_copy.h b/src/external_copy/external_copy.h index 62a4f1b2..4a8689fb 100644 --- a/src/external_copy/external_copy.h +++ b/src/external_copy/external_copy.h @@ -105,13 +105,22 @@ class ExternalCopyArrayBufferView : public ExternalCopy { enum class ViewType { Uint8, Uint8Clamped, Int8, Uint16, Int16, Uint32, Int32, Float32, Float64, BigInt64, BigUint64, DataView }; private: - std::unique_ptr buffer; + bool is_node_buffer; ViewType type; size_t byte_offset, byte_length; + v8::Local underlying_buffer; + v8::Local own_properties; + public: - ExternalCopyArrayBufferView(std::unique_ptr buffer, ViewType type, size_t byte_offset, size_t byte_length); - auto CopyInto(bool transfer_in = false) -> v8::Local final; + ExternalCopyArrayBufferView(ViewType type, size_t byte_offset, size_t byte_length, bool is_node_buffer = false); + auto CopyInto(bool transfer_in) -> v8::Local final; + + auto SetUnderlyingBuffer(v8::Local buffer) -> void { underlying_buffer = buffer; } + auto SetOwnProperties(v8::Local properties) -> void { own_properties = properties; } + + static auto Copy(v8::Local view) -> std::unique_ptr; + static auto CopyOwnProperties(v8::Isolate* isolate, v8::Local view) -> v8::Local; }; } // namespace ivm diff --git a/src/external_copy/serializer.h b/src/external_copy/serializer.h index ce6c84c4..00846de2 100644 --- a/src/external_copy/serializer.h +++ b/src/external_copy/serializer.h @@ -12,12 +12,14 @@ class SerializationDelegateBase { public: SerializationDelegateBase( std::deque>& transferables, + std::deque& array_buffer_view_indexes, std::deque& wasm_modules - ) : transferables{transferables}, wasm_modules{wasm_modules} {} + ) : transferables{transferables}, array_buffer_view_indexes{array_buffer_view_indexes}, wasm_modules{wasm_modules} {} protected: - std::deque>& transferables; - std::deque& wasm_modules; + std::deque>& transferables; + std::deque& array_buffer_view_indexes; + std::deque& wasm_modules; }; class SerializerDelegate : public SerializationDelegateBase, public v8::ValueSerializer::Delegate { @@ -33,6 +35,8 @@ class SerializerDelegate : public SerializationDelegateBase, public v8::ValueSer v8::Isolate* isolate, v8::Local module) -> v8::Maybe final; auto WriteHostObject(v8::Isolate* isolate, v8::Local object) -> v8::Maybe final; + auto WriteArrayBufferView(v8::Isolate* isolate, v8::Local view) -> v8::Maybe; + private: v8::ValueSerializer* serializer = nullptr; }; @@ -49,6 +53,8 @@ class DeserializerDelegate : public SerializationDelegateBase, public v8::ValueD v8::Isolate* isolate, uint32_t transfer_id) -> v8::MaybeLocal final; auto ReadHostObject(v8::Isolate* isolate) -> v8::MaybeLocal final; + auto ReadArrayBufferView(v8::Isolate* isolate, uint32_t transferable_id) -> void; + private: v8::ValueDeserializer* deserializer = nullptr; }; @@ -65,8 +71,9 @@ class BaseSerializer { // Initialize serializer and delegate auto* isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); - detail::SerializerDelegate delegate{transferables, wasm_modules}; + detail::SerializerDelegate delegate{transferables, array_buffer_view_indexes, wasm_modules}; v8::ValueSerializer serializer{isolate, &delegate}; + serializer.SetTreatArrayBufferViewsAsHostObjects(true); delegate.SetSerializer(&serializer); // Run serialization @@ -84,7 +91,7 @@ class BaseSerializer { // Initialize deserializer and delegate auto* isolate = v8::Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); - detail::DeserializerDelegate delegate{transferables, wasm_modules}; + detail::DeserializerDelegate delegate{transferables, array_buffer_view_indexes, wasm_modules}; v8::ValueDeserializer deserializer{isolate, buffer.get(), size, &delegate}; delegate.SetDeserializer(&deserializer); @@ -109,6 +116,7 @@ class BaseSerializer { std::unique_ptr buffer = {nullptr, std::free}; std::deque> transferables; std::deque wasm_modules; + std::deque array_buffer_view_indexes; size_t size; }; diff --git a/src/external_copy/serializer_nortti.cc b/src/external_copy/serializer_nortti.cc index f2ce0981..4c860a0b 100644 --- a/src/external_copy/serializer_nortti.cc +++ b/src/external_copy/serializer_nortti.cc @@ -1,6 +1,7 @@ #include "serializer.h" #include "isolate/functor_runners.h" #include "module/transferable.h" +#include "isolate/generic/object.h" /** * This file is compiled *without* runtime type information, which matches the nodejs binary and @@ -33,21 +34,51 @@ auto SerializerDelegate::GetWasmModuleTransferId( return result; } -auto SerializerDelegate::WriteHostObject(Isolate* /*isolate*/, Local object) -> Maybe { +auto SerializerDelegate::WriteHostObject(Isolate* isolate, Local object) -> Maybe { auto result = Nothing(); detail::RunBarrier([&]() { - serializer->WriteUint32(transferables.size()); - transferables.emplace_back(TransferOut(object)); - result = Just(true); + auto host_object_id = transferables.size(); + serializer->WriteUint32(host_object_id); + + if (object->IsArrayBufferView()) { + auto view = object.As(); + transferables.emplace_back(ExternalCopyArrayBufferView::Copy(view)); + array_buffer_view_indexes.emplace_back(host_object_id); + result = WriteArrayBufferView(isolate, view); + } else { + transferables.emplace_back(TransferOut(object)); + result = Just(true); + } }); return result; } -auto DeserializerDelegate::ReadHostObject(Isolate* /*isolate*/) -> MaybeLocal { +auto SerializerDelegate::WriteArrayBufferView(v8::Isolate* isolate, v8::Local view) -> v8::Maybe { + Local context = isolate->GetCurrentContext(); + auto result = serializer->WriteValue(context, view->Buffer()); + + if (result.IsJust()) { + auto properties = ExternalCopyArrayBufferView::CopyOwnProperties(isolate, view); + return serializer->WriteValue(context, properties); + } + + return result; +} + +auto DeserializerDelegate::ReadHostObject(Isolate* isolate) -> MaybeLocal { MaybeLocal result; detail::RunBarrier([&]() { uint32_t ii; assert(deserializer->ReadUint32(&ii)); + + // Replacing std::find with a for loop for compatibility with Alpine Linux + for (const auto& value : array_buffer_view_indexes) { + if (value == ii) { + ReadArrayBufferView(isolate, ii); + break; + } + } + result = transferables[ii]->TransferIn().As(); }); return result; @@ -71,5 +102,15 @@ auto DeserializerDelegate::GetWasmModuleFromId( return result; } +auto DeserializerDelegate::ReadArrayBufferView(Isolate* isolate, uint32_t transferable_id) -> void { + auto *view_copy = reinterpret_cast*>(&transferables[transferable_id])->get(); + Local context = isolate->GetCurrentContext(); + auto underlying_buffer = deserializer->ReadValue(context).ToLocalChecked().As(); + auto properties = deserializer->ReadValue(context).ToLocalChecked().As(); + + view_copy->SetUnderlyingBuffer(underlying_buffer); + view_copy->SetOwnProperties(properties); +} + } // namespace detail } // namespace ivm diff --git a/src/isolate/environment.cc b/src/isolate/environment.cc index 396ab892..8daacac6 100644 --- a/src/isolate/environment.cc +++ b/src/isolate/environment.cc @@ -102,7 +102,11 @@ void IsolateEnvironment::HeapCheck::Epilogue() { env.hit_memory_limit = true; env.Terminate(); throw FatalRuntimeError("Isolate was disposed during execution due to memory limit"); + } else { + env.ReportExternalMemoryToParentIsolate(heap); } + } else { + env.ReportExternalMemoryToParentIsolate(heap); } } } @@ -181,12 +185,46 @@ auto IsolateEnvironment::CodeGenCallback2(Local context, Local s return CodeGenCallback(context, source); } +void IsolateEnvironment::MarkSweepCompactPrologue(Isolate* /*isolate*/, GCType gc_type, GCCallbackFlags /*gc_flags*/, void* data) { +#if V8_AT_LEAST(10, 0, 0) // Node.js 18+ + if ((gc_type & (GCType::kGCTypeMarkSweepCompact | GCType::kGCTypeMinorMarkCompact)) == 0) { + return; + } +#else + if (gc_type != GCType::kGCTypeMarkSweepCompact) { + return; + } +#endif + + auto* that = static_cast(data); + + if (!that->owned_isolates) { + return; + } + + auto isolates = *that->owned_isolates->read(); // copy + for (const auto& handle: isolates) { + auto ref = handle.holder.lock(); + if (!ref) { + continue; + } + + auto owned_isolate = ref->GetIsolate(); + if (owned_isolate && owned_isolate->last_memory_pressure != MemoryPressureLevel::kCritical) { + Executor::Lock lock(*owned_isolate); + owned_isolate->memory_pressure = MemoryPressureLevel::kCritical; + owned_isolate->CheckMemoryPressure(); + } + } +} + void IsolateEnvironment::MarkSweepCompactEpilogue(Isolate* isolate, GCType /*gc_type*/, GCCallbackFlags gc_flags, void* data) { auto* that = static_cast(data); HeapStatistics heap; that->isolate->GetHeapStatistics(&heap); size_t total_memory = heap.used_heap_size() + that->extra_allocated_memory; size_t memory_limit = that->memory_limit + that->misc_memory_size; + that->ReportExternalMemoryToParentIsolate(heap); if (total_memory > memory_limit) { if ((gc_flags & (GCCallbackFlags::kGCCallbackFlagCollectAllAvailableGarbage | GCCallbackFlags::kGCCallbackFlagForced)) == 0) { // Force full garbage collection @@ -213,7 +251,6 @@ void IsolateEnvironment::MarkSweepCompactEpilogue(Isolate* isolate, GCType /*gc_ } else { that->RequestMemoryPressureNotification(MemoryPressureLevel::kNone); } - return; } } @@ -352,6 +389,7 @@ IsolateEnvironment::IsolateEnvironment(UvScheduler& default_scheduler) : void IsolateEnvironment::IsolateCtor(Isolate* isolate, Local context) { this->isolate = isolate; + isolate->AddGCPrologueCallback(MarkSweepCompactPrologue, static_cast(this), GCType::kGCTypeAll); default_context.Reset(isolate, context); } @@ -391,6 +429,7 @@ void IsolateEnvironment::IsolateCtor(size_t memory_limit_in_mb, shared_ptrSetModifyCodeGenerationFromStringsCallback(CodeGenCallback2); // Add GC callbacks + isolate->AddGCPrologueCallback(MarkSweepCompactPrologue, static_cast(this), GCType::kGCTypeAll); isolate->AddGCEpilogueCallback(MarkSweepCompactEpilogue, static_cast(this), GCType::kGCTypeMarkSweepCompact); isolate->AddNearHeapLimitCallback(NearHeapLimitCallback, static_cast(this)); @@ -430,6 +469,17 @@ IsolateEnvironment::~IsolateEnvironment() { handle.dispose_wait->Join(); } } else { + { + auto parent_isolate_holder = parent_holder.lock(); + if (parent_isolate_holder) { + auto parent_isolate = parent_isolate_holder->GetIsolate(); + if (parent_isolate) { + parent_isolate->GetIsolate()->AdjustAmountOfExternalAllocatedMemory( + -(ssize_t) last_reported_external_memory_size + ); + } + } + } { // Grab local pointer to inspector agent with scheduler lock active auto agent_ptr = [&]() { @@ -454,6 +504,8 @@ IsolateEnvironment::~IsolateEnvironment() { ExchangeDefault(scheduler_lock->sync_interrupts); ExchangeDefault(scheduler_lock->handle_tasks); ExchangeDefault(scheduler_lock->tasks); + + buffer_prototype.Reset(); } { std::lock_guard allocator_lock{isolate_allocator_mutex}; diff --git a/src/isolate/environment.h b/src/isolate/environment.h index a02b35fd..cfcd3559 100644 --- a/src/isolate/environment.h +++ b/src/isolate/environment.h @@ -89,6 +89,7 @@ class IsolateEnvironment { Executor executor; std::shared_ptr dispose_wait{std::make_shared()}; std::weak_ptr holder; + std::weak_ptr parent_holder; std::shared_ptr task_runner; std::unique_ptr inspector_agent; v8::Persistent default_context; @@ -99,6 +100,7 @@ class IsolateEnvironment { size_t memory_limit = 0; size_t initial_heap_size_limit = 0; size_t misc_memory_size = 0; + size_t last_reported_external_memory_size = 0; std::atomic extra_allocated_memory = 0; v8::MemoryPressureLevel memory_pressure = v8::MemoryPressureLevel::kNone; v8::MemoryPressureLevel last_memory_pressure = v8::MemoryPressureLevel::kNone; @@ -115,6 +117,8 @@ class IsolateEnvironment { std::unordered_map*, std::pair> weak_persistents; std::shared_ptr cpu_profile_manager; + v8::Global buffer_prototype; + public: RemoteHandle error_handler; std::unordered_multimap module_handles; @@ -149,6 +153,7 @@ class IsolateEnvironment { /** * GC hooks to kill this isolate before it runs out of memory */ + static void MarkSweepCompactPrologue(v8::Isolate* isolate, v8::GCType gc_type, v8::GCCallbackFlags gc_flags, void* data); static void MarkSweepCompactEpilogue(v8::Isolate* isolate, v8::GCType gc_type, v8::GCCallbackFlags gc_flags, void* data); static auto NearHeapLimitCallback(void* data, size_t current_heap_limit, size_t initial_heap_limit) -> size_t; void RequestMemoryPressureNotification(v8::MemoryPressureLevel memory_pressure, bool as_interrupt = false); @@ -190,6 +195,7 @@ class IsolateEnvironment { auto env = std::make_shared(static_cast(*Executor::GetDefaultEnvironment().scheduler)); auto holder = std::make_shared(env); env->holder = holder; + env->parent_holder = IsolateEnvironment::GetCurrentHolder(); env->IsolateCtor(memory_limit_in_mb, std::move(snapshot_blob), snapshot_length); return holder; } @@ -311,6 +317,36 @@ class IsolateEnvironment { extra_allocated_memory += size; } + void ReportExternalMemoryToParentIsolate(v8::HeapStatistics &heap) { + if (nodejs_isolate) { + return; + } + + auto parent_isolate_holder = this->parent_holder.lock(); + if (!parent_isolate_holder) { + return; + } + + size_t new_external_memory_size = + heap.total_heap_size() + + heap.external_memory() + + heap.malloced_memory() + + extra_allocated_memory; + auto delta = static_cast(new_external_memory_size - last_reported_external_memory_size); + last_reported_external_memory_size = new_external_memory_size; + + static_assert(sizeof(ssize_t) <= sizeof(void*), "ssize_t does not fit into a void*"); + + if (delta != 0) { + auto parent_env = parent_isolate_holder->GetIsolate(); + if (parent_env) { + parent_env->isolate->RequestInterrupt([](v8::Isolate* isolate, void* delta) { + isolate->AdjustAmountOfExternalAllocatedMemory(reinterpret_cast(delta)); + }, reinterpret_cast(delta)); // NOLINT(*-no-int-to-ptr) + } + } + } + /** * Returns the current number of outstanding RemoteHandles<> to this isolate. */ @@ -350,8 +386,17 @@ class IsolateEnvironment { */ void AddWeakCallback(v8::Persistent* handle, void(*fn)(void*), void* param); void RemoveWeakCallback(v8::Persistent* handle); + + inline auto GetBufferPrototype() const -> v8::Local { + return buffer_prototype.Get(isolate); + } + inline void SetBufferPrototype(v8::Local value) { + buffer_prototype.Reset(isolate, value); + } }; + + template template auto IsolateSpecific::Deref(Functor callback) -> v8::Local { diff --git a/src/isolate/functor_runners.h b/src/isolate/functor_runners.h index 7eeb1f4d..2fc9666d 100644 --- a/src/isolate/functor_runners.h +++ b/src/isolate/functor_runners.h @@ -39,6 +39,11 @@ inline void RunCatchExternal(v8::Local default_context, F1 fn1, F2 try { try { fn1(); + + if (try_catch.HasCaught()) { + v8::Context::Scope context_scope{default_context}; + fn2(ExternalCopy::CopyThrownValue(try_catch.Exception())); + } } catch (const RuntimeTypeError& cc_error) { // The following errors are just various C++ strings with an error type fn2(std::make_unique(ExternalCopyError::ErrorType::TypeError, cc_error.GetMessage().c_str(), cc_error.GetStackTrace())); diff --git a/src/isolate/generic/object.h b/src/isolate/generic/object.h new file mode 100644 index 00000000..fd8f7225 --- /dev/null +++ b/src/isolate/generic/object.h @@ -0,0 +1,33 @@ +#pragma once +#include + +namespace ivm { + inline auto GetObjectOwnProperties(v8::Local context, v8::Local object) -> v8::Local { + v8::Local property_names = object->GetPropertyNames(context, v8::KeyCollectionMode::kOwnOnly, v8::ALL_PROPERTIES, v8::IndexFilter::kSkipIndices).ToLocalChecked(); + + return property_names; + } + + inline void CopyObjectProperties(v8::Local context, v8::Local target, v8::Local source, v8::Local property_names) { + for (uint32_t i = 0; i < property_names->Length(); i++) { + v8::MaybeLocal key = property_names->Get(context, i); + if (key.IsEmpty()) { + continue; + } + + v8::MaybeLocal value = source->Get(context, key.ToLocalChecked()); + + if (value.IsEmpty()) { + continue; + } + + target->Set(context, key.ToLocalChecked(), value.ToLocalChecked()).Check(); + } + } + + inline void CopyObjectProperties(v8::Local context, v8::Local target, v8::Local source) { + auto property_names = GetObjectOwnProperties(context, source); + + CopyObjectProperties(context, target, source, property_names); + } +} \ No newline at end of file diff --git a/src/isolate/node_wrapper.h b/src/isolate/node_wrapper.h index afa3981f..59ef3b13 100644 --- a/src/isolate/node_wrapper.h +++ b/src/isolate/node_wrapper.h @@ -3,13 +3,17 @@ #ifdef __clang__ #pragma clang system_header #include +#include #elif __GNUC__ #pragma GCC system_header #include +#include #elif _MSC_VER #pragma warning(push, 0) #include +#include #pragma warning(pop) #else #include +#include #endif diff --git a/src/module/isolate.cc b/src/module/isolate.cc index 548dcbcc..15633167 100644 --- a/src/module/isolate.cc +++ b/src/module/isolate.cc @@ -99,6 +99,9 @@ void init(Local target) { auto isolates = default_isolates->write(); assert(isolates->find(isolate) == isolates->end()); auto holder = IsolateEnvironment::New(isolate, context); + auto dummy_buffer = node::Buffer::New(isolate, 0).ToLocalChecked(); + assert(dummy_buffer->GetPrototype()->IsObject()); + holder->GetIsolate()->SetBufferPrototype(dummy_buffer->GetPrototype().As()); isolates->insert(std::make_pair( isolate, IsolateHolderAndJoin{holder, holder->GetIsolate()->GetDisposeWaitHandle()} diff --git a/src/module/isolate_handle.cc b/src/module/isolate_handle.cc index 830a2d0a..9d88ebc3 100644 --- a/src/module/isolate_handle.cc +++ b/src/module/isolate_handle.cc @@ -58,7 +58,8 @@ auto IsolateHandle::Definition() -> Local { "referenceCount", MemberAccessor{}, "wallTime", MemberAccessor{}, "startCpuProfiler", MemberFunction{}, - "stopCpuProfiler", MemberFunction), &IsolateHandle::StopCpuProfiler<1>>{} + "stopCpuProfiler", MemberFunction), &IsolateHandle::StopCpuProfiler<1>>{}, + "setBufferPrototype", MemberFunction{} )); } @@ -433,6 +434,19 @@ auto IsolateHandle::StopCpuProfiler(v8::Local title) -> Local return ThreePhaseTask::Run(*isolate, dest); } +/** + * Reference count + */ +auto IsolateHandle::SetBufferPrototype(Local prototype) -> Local { + auto env = this->isolate->GetIsolate(); + if (!env) { + throw RuntimeGenericError("Isolate is disposed"); + } + env->SetBufferPrototype(prototype); + + return Undefined(Isolate::GetCurrent()); +} + /** * Reference count */ diff --git a/src/module/isolate_handle.h b/src/module/isolate_handle.h index 18c71d95..d105b801 100644 --- a/src/module/isolate_handle.h +++ b/src/module/isolate_handle.h @@ -38,6 +38,8 @@ class IsolateHandle : public TransferableHandle { auto GetWallTime() -> v8::Local; auto StartCpuProfiler(v8::Local title) -> v8::Local; template auto StopCpuProfiler(v8::Local title) -> v8::Local; + + auto SetBufferPrototype(v8::Local prototype) -> v8::Local; auto GetReferenceCount() -> v8::Local; auto IsDisposedGetter() -> v8::Local; diff --git a/tests/buffer-copy.js b/tests/buffer-copy.js new file mode 100644 index 00000000..c6679fd6 --- /dev/null +++ b/tests/buffer-copy.js @@ -0,0 +1,81 @@ +'use strict'; +let ivm = require('isolated-vm'); +let assert = require('assert'); + +const bufferViews = [ + new DataView(new ArrayBuffer(42)), + new Int8Array([1, 2, 3]), + new Int16Array([1, 2, 3]), + new Int32Array([1, 2, 3]), + new Uint8Array([1, 2, 3]), + new Uint8ClampedArray([1, 2, 3]), + new Uint16Array([1, 2, 3]), + new Uint32Array([1, 2, 3]), + new Float32Array([1, 2, 3]), + new Float64Array([1, 2, 3]), + new BigInt64Array([1n, 2n, 3n]), + new BigUint64Array([1n, 2n, 3n]), +]; + +for (const buffer of bufferViews) { + const isolate = new ivm.Isolate(); + + const context = isolate.createContextSync(); + const jail = context.global; + + context.evalSync(`function identity(param) { return param; }`); + + const identityFunctionRef = jail.getSync('identity', { reference: true }); + + // copy without properties + const resultWithoutProps = identityFunctionRef.applySync(null, [buffer], { result: { copy: true }, arguments: { copy: true } }); + assert.deepStrictEqual(buffer, resultWithoutProps); + + // copy with extra properties + buffer.stringProp = "string"; + buffer.numberProp = 42; + buffer.boolProp = true; + buffer.nullProp = null; + buffer.undefinedProp = undefined; + buffer.bigIntProp = 42n; + buffer.emptyArrayProp = []; + buffer.arrayProp = ["string", 42, true, null, undefined, 42n, [], {}]; + buffer.emptyObjectProp = {}; + buffer.objectProp = { + stringProp: "string", + numberProp: 42, + boolProp: true, + nullProp: null, + undefinedProp: undefined, + bigIntProp: 42n, + arrayProp: [], + nested: { + prop: "nested" + } + } + + const resultWithProps = identityFunctionRef.applySync(null, [buffer], { result: { copy: true }, arguments: { copy: true } }); + assert.deepStrictEqual(buffer, resultWithProps); + + // fail to copy with function properties to the Isolate + buffer.functionProp = () => {}; + try { + identityFunctionRef.applySync(null, [buffer], { result: { copy: true }, arguments: { copy: true } }); + assert.fail("Expected an error"); + } catch (error) { + assert.strictEqual(error.message, "() => {} could not be cloned."); + } + + // fail to copy with function properties from the Isolate + const constructorName = buffer.constructor.name; + context.evalSync(`function getBuffer() { const buffer = new ${constructorName}(new ArrayBuffer(8)); buffer.x = { functionProp() {} }; return buffer; }`); + const getBufferFunctionRef = jail.getSync('getBuffer', { reference: true }); + try { + getBufferFunctionRef.applySync(null, [], { result: { copy: true } }); + assert.fail("Expected an error"); + } catch (error) { + assert.strictEqual(error.message, "functionProp() {} could not be cloned."); + } +} + +console.log('pass'); \ No newline at end of file diff --git a/tests/cached-data.js b/tests/cached-data.js index 5d851031..7ec858f7 100644 --- a/tests/cached-data.js +++ b/tests/cached-data.js @@ -35,24 +35,28 @@ let cachedData; } { + const count = 3; // larger number causes GC to run and mess up the results + // Time compilation with no cached data - let d = Date.now(); - const timeout = 200; - let count = 0; - do { + const uncachedCompileStart = Date.now(); + for (let i = 0; i < count; i++) { const isolate = new ivm.Isolate; const script = isolate.compileScriptSync(src); - ++count; - } while (Date.now() < d + timeout); + } + const uncachedCompileEnd = Date.now(); + const uncachedCompileDuration = uncachedCompileEnd - uncachedCompileStart; - // Compare to compilation with cached data - d = Date.now(); - for (let ii = 0; ii < count; ++ii) { + // Time compilation with cached data + const cachedCompileStart = Date.now(); + for (let i = 0; i < count; i++) { const isolate = new ivm.Isolate; const script = isolate.compileScriptSync(src, { cachedData }); assert.ok(!script.cachedDataRejected); } - if (Date.now() - d > timeout / 2) { + const cachedCompileEnd = Date.now(); + const cachedCompileDuration = cachedCompileEnd - cachedCompileStart; + + if (cachedCompileDuration > uncachedCompileDuration * 0.7) { console.log('cached data is suspiciously slow'); } } diff --git a/tests/garbage-collection-propagation.js b/tests/garbage-collection-propagation.js new file mode 100644 index 00000000..1e7907f6 --- /dev/null +++ b/tests/garbage-collection-propagation.js @@ -0,0 +1,29 @@ +const ivm = require('isolated-vm'); + +async function run() { + for (let i = 0; i < 1000; i++) { + const isolate = new ivm.Isolate; + const context = isolate.createContextSync(); + const jail = context.global; + jail.setSync('isolate', isolate); + + isolate.compileScriptSync(` + isolate.compileScriptSync('1'); + delete globalThis.isolate; + `).runSync(context); + + void jail; + + await new Promise(resolve => setTimeout(resolve, 1)); + } + + const memoryUsage = process.memoryUsage(); + const rssMemory = Math.round(memoryUsage.rss / 1024 / 1024); + if (rssMemory < 200) { + console.log('pass'); + } else { + console.log('failed with RSS memory:', rssMemory); + } +} + +run().catch(console.error); \ No newline at end of file diff --git a/tests/garbage-collection.js b/tests/garbage-collection.js new file mode 100644 index 00000000..10fef998 --- /dev/null +++ b/tests/garbage-collection.js @@ -0,0 +1,23 @@ +const ivm = require('isolated-vm'); + +async function run() { + for (let i = 0; i < 1000; i++) { + const isolate = new ivm.Isolate; + const context = isolate.createContextSync(); + const jail = context.global; + + void jail; + + await new Promise(resolve => setTimeout(resolve, 1)); + } + + const memoryUsage = process.memoryUsage(); + const rssMemory = Math.round(memoryUsage.rss / 1024 / 1024); + if (rssMemory < 200) { + console.log('pass'); + } else { + console.log('failed with RSS memory:', rssMemory); + } +} + +run().catch(console.error); \ No newline at end of file diff --git a/tests/node-buffer-prototype.js b/tests/node-buffer-prototype.js new file mode 100644 index 00000000..610b12b7 --- /dev/null +++ b/tests/node-buffer-prototype.js @@ -0,0 +1,74 @@ +'use strict'; +let ivm = require('isolated-vm'); +let assert = require('assert'); + +let arr = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 ]; +const buffer = Buffer.from(arr) + +// Without buffer prototype set, buffers copied from isolate are pure Uint8Array instances +{ + const isolate = new ivm.Isolate(); + const context = isolate.createContextSync(); + const jail = context.global; + + jail.setSync('buffer', buffer, { copy: true }); + + const isUint8ArrayInSandbox = context.evalClosureSync(` + return buffer instanceof Uint8Array && buffer.constructor.name === 'Uint8Array' + `, [], { result: { copy: true } }); + + assert(isUint8ArrayInSandbox === true); + + const bufferCopy = jail.getSync('buffer', { copy: true }); + + assert(bufferCopy !== buffer); + assert(bufferCopy instanceof Uint8Array); + assert(!Buffer.isBuffer(bufferCopy)); +} + +{ + const isolate = new ivm.Isolate(); + const context = isolate.createContextSync(); + const jail = context.global; + + context.evalClosureSync(` + class Buffer extends Uint8Array {} + globalThis.Buffer = Buffer; + $0.setBufferPrototype(Buffer.prototype); + `, [isolate]); + + jail.setSync('buffer', buffer, { copy: true }); + + const isBufferObjectInSandbox = context.evalClosureSync(` + return buffer instanceof Buffer && buffer.constructor.name === 'Buffer' + `, [], { result: { copy: true } }); + + assert(isBufferObjectInSandbox === true); + + const bufferCopy = jail.getSync('buffer', { copy: true }); + + assert(bufferCopy !== buffer); + assert(Buffer.isBuffer(bufferCopy)); + assert.deepStrictEqual(bufferCopy, buffer); +} + +{ + const isolate = new ivm.Isolate(); + const context = isolate.createContextSync(); + const jail = context.global; + + context.evalClosureSync(` + class Buffer extends Uint8Array {} + globalThis.Buffer = Buffer; + $0.setBufferPrototype(Buffer.prototype); + `, [isolate]); + + jail.setSync('objectWithBuffer', { buffer, array: [buffer] }, { copy: true }); + + const objectWithBuffer = jail.getSync('objectWithBuffer', { copy: true }); + + assert(Buffer.isBuffer(objectWithBuffer.buffer)); + assert(Buffer.isBuffer(objectWithBuffer.array[0])); +} + +console.log('pass'); diff --git a/tests/orphaned-isolate-gc.js b/tests/orphaned-isolate-gc.js index 1d466ffe..d845a9e2 100644 --- a/tests/orphaned-isolate-gc.js +++ b/tests/orphaned-isolate-gc.js @@ -1,5 +1,11 @@ // node-args: --expose-gc const ivm = require('isolated-vm'); + +if (process.memoryUsage().rss === 0) { + console.log('pass'); + return; +} + let isolate = Array(100).fill().map(() => new ivm.Isolate()); gc(); let rss1 = process.memoryUsage().rss; diff --git a/tests/proxy-copy.js b/tests/proxy-copy.js new file mode 100644 index 00000000..dd9072b5 --- /dev/null +++ b/tests/proxy-copy.js @@ -0,0 +1,48 @@ +'use strict'; +let ivm = require('isolated-vm'); +let assert = require('assert'); + +// Copying Proxy Object directly +{ + const isolate = new ivm.Isolate(); + const context = isolate.createContextSync(); + const jail = context.global; + + const targetObject = { property: 'value' }; + const proxyObject = new Proxy(targetObject, {}); + + jail.setSync('proxyObject', proxyObject, { copy: true }); + + const isProxyObjectInSandbox = context.evalClosureSync(` + return proxyObject.property === 'value' + `, [], { result: { copy: true } }); + + assert(isProxyObjectInSandbox === true); + + const proxyObjectCopy = jail.getSync('proxyObject', { copy: true }); + + assert(proxyObjectCopy !== proxyObject); + assert.deepStrictEqual(targetObject, proxyObjectCopy) +} + +// Copy nested Proxy Object will fail, unfortunately V8 serializer will throw on Proxy object, and +// it looks like there is no way to enable proxy serialization. +{ + const isolate = new ivm.Isolate(); + const context = isolate.createContextSync(); + const jail = context.global; + + const targetObject = { property: 'value' }; + const proxyObject = new Proxy(targetObject, {}); + + const objectWithNesterProxyObject = { proxyObject }; + + try { + jail.setSync('objectWithNesterProxyObject', objectWithNesterProxyObject, { copy: true }); + assert(false); + } catch (e) { + assert(e instanceof TypeError); + } +} + +console.log('pass'); diff --git a/tests/workers-threads.js b/tests/workers-threads.js index 0922236e..47a67359 100644 --- a/tests/workers-threads.js +++ b/tests/workers-threads.js @@ -6,7 +6,7 @@ const ivm = require('isolated-vm'); const assert = require('assert'); const { Worker, isMainThread, parentPort } = require('worker_threads'); -const kIsolateCount = 100; +const kIsolateCount = 50; const kWorkerCount = 10; if (isMainThread) { Promise.all(Array(kWorkerCount).fill().map(async (_, ii) => {