diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f04d2c139 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..a420a61ab --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + all: + patterns: + - '*' + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + groups: + all: + patterns: + - '*' + + - package-ecosystem: npm + directory: /test/addon_build/tpl + schedule: + interval: weekly + groups: + all: + patterns: + - '*' diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml new file mode 100644 index 000000000..3bd60207a --- /dev/null +++ b/.github/workflows/ci-win.yml @@ -0,0 +1,77 @@ +name: Node.js CI Windows Platform + +on: [push, pull_request] + +env: + PYTHON_VERSION: '3.11' + +permissions: + contents: read + +jobs: + test: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + api_version: + - standard + - experimental + node-version: + - 20.x + - 22.x + - 24.x + - 25.x + - 26.x + architecture: [x64, x86] + os: + - windows-2022 + - windows-2025 + exclude: + # Skip when node 24.x or 25.x AND architecture is x86 since there is + # no published Node.js x86 build for those versions. + - node-version: 24.x + architecture: x86 + - node-version: 25.x + architecture: x86 + - node-version: 26.x + architecture: x86 + runs-on: ${{ matrix.os }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ matrix.architecture }} + - name: Check Node.js installation + run: | + node --version + npm --version + - name: Install dependencies + run: | + npm install + # node-gyp@12 (from package.json) supports Visual Studio 2026, but only + # node-gyp@13 emits the linker options that Node.js 26 builds require + # (older node-gyp trips LNK1117 on '/opt:lldltojobs'). Upgrade in place for + # Node.js >= 26; other versions keep node-gyp@12. + - name: Use node-gyp@13 for Node.js >= 26 + if: matrix.node-version == '26.x' + run: npm install --no-save node-gyp@13 + - name: npm test + shell: bash + run: | + if [ "${{ matrix.api_version }}" = "experimental" ]; then + export NAPI_VERSION=2147483647 + fi + npm run pretest -- --verbose + node test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 193f09384..c583b8c8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,54 +1,81 @@ -name: Node.js CI +name: Node.js CI Unix Platform on: [push, pull_request] +env: + PYTHON_VERSION: '3.11' + +permissions: + contents: read + jobs: test: - timeout-minutes: 30 + timeout-minutes: 60 strategy: + fail-fast: false matrix: + api_version: + - standard + - experimental node-version: - - node/10 - - node/12 - - node/14 - - node/15 - compiler: - - gcc - - clang + - 20.x + - 22.x + - 24.x + - 25.x + - 26.x os: - - ubuntu-16.04 # ubuntu-18.04/ubuntu-latest missing package g++-4.9 - macos-latest + - ubuntu-latest + compiler: + - clang + - gcc + exclude: + - os: macos-latest + compiler: gcc # GCC is an alias for clang on the MacOS image. runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: Install system dependencies - run: | - if [ "${{ matrix.compiler }}" = "gcc" -a "${{ matrix.os }}" = ubuntu-* ]; then - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install g++-4.9 - fi + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ matrix.node-version }} + - name: Check Node.js installation run: | - git clone --branch v1.4.2 --depth 1 https://github.com/jasongin/nvs ~/.nvs - . ~/.nvs/nvs.sh - nvs --version - nvs add ${{ matrix.node-version }} - nvs use ${{ matrix.node-version }} node --version npm --version + - name: Install dependencies + run: | npm install + # Node.js >= 26 requires node-gyp@13; older versions keep node-gyp@12 + # (from package.json). + - name: Use node-gyp@13 for Node.js >= 26 + if: matrix.node-version == '26.x' + run: npm install --no-save node-gyp@13 - name: npm test run: | + if [ "${{ matrix.api_version }}" = "experimental" ]; then + export NAPI_VERSION=2147483647 + fi if [ "${{ matrix.compiler }}" = "gcc" ]; then export CC="gcc" CXX="g++" fi - if [ "${{ matrix.compiler }}" = "gcc" -a "${{ matrix.os }}" = ubuntu-* ]; then - export CC="gcc-4.9" CXX="g++-4.9" AR="gcc-ar-4.9" RANLIB="gcc-ranlib-4.9" NM="gcc-nm-4.9" - fi if [ "${{ matrix.compiler }}" = "clang" ]; then export CC="clang" CXX="clang++" fi + echo "CC=\"$CC\" CXX=\"$CXX\"" + echo "$CC --version" + $CC --version + echo "$CXX --version" + $CXX --version export CFLAGS="$CFLAGS -O3 --coverage" LDFLAGS="$LDFLAGS --coverage" echo "CFLAGS=\"$CFLAGS\" LDFLAGS=\"$LDFLAGS\"" npm run pretest -- --verbose diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..8758fdcef --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,85 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: ["main"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] + schedule: + - cron: "0 0 * * 1" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["cpp", "javascript"] + # CodeQL supports [ $supported-codeql-languages ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + - name: Use Node.js v18.x + if: matrix.language == 'cpp' + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 18.x + + - name: Build cpp + if: matrix.language == 'cpp' + run: | + npx node-gyp rebuild -C test + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml new file mode 100644 index 000000000..3d116e405 --- /dev/null +++ b/.github/workflows/coverage-linux.yml @@ -0,0 +1,68 @@ +name: Coverage Linux + +on: + pull_request: + types: [opened, synchronize, reopened] + paths-ignore: + - '**.md' + - benchmark/** + - doc/** + - tools/** + - unit-test/** + - .github/** + - '!.github/workflows/coverage-linux.yml' + push: + branches: + - main + paths-ignore: + - '**.md' + - benchmark/** + - doc/** + - tools/** + - unit-test/** + - .github/** + - '!.github/workflows/coverage-linux.yml' + +env: + PYTHON_VERSION: '3.11' + NODE_VERSION: '22.x' + +permissions: + contents: read + +jobs: + coverage-linux: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Environment Information + run: npx envinfo + - name: Install gcovr + run: pip install gcovr==6.0 + - name: Install dependencies + run: npm install + - name: Test with coverage + run: | + npm run create-coverage + - name: Generate coverage report (XML) + run: | + npm run report-coverage-xml + - name: Upload + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + directory: ./coverage-xml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..8d6513bed --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: 'Checkout Repository' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 6fd42ec4b..4cd921796 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -2,23 +2,31 @@ name: Style Checks on: [push, pull_request] +permissions: + contents: read + jobs: lint: if: github.repository == 'nodejs/node-addon-api' strategy: matrix: - node-version: [14.x] + node-version: [22.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - run: git branch -a - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} - run: npm install - - run: CLANG_FORMAT_START=refs/remotes/origin/master npm run lint + - run: FORMAT_START=refs/remotes/origin/main npm run lint diff --git a/.github/workflows/node-api-headers.yml b/.github/workflows/node-api-headers.yml new file mode 100644 index 000000000..59c686348 --- /dev/null +++ b/.github/workflows/node-api-headers.yml @@ -0,0 +1,71 @@ +name: Node.js CI with node-api-headers + +on: [push, pull_request] + +env: + PYTHON_VERSION: '3.11' + +permissions: + contents: read + +jobs: + test: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + api_version: + - '9' + node-version: + - 22.x + node-api-headers-version: + - '1.1.0' + - '1.2.0' + - '1.3.0' + os: + - ubuntu-latest + compiler: + - gcc + - clang + runs-on: ${{ matrix.os }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ matrix.node-version }} + - name: Check Node.js installation + run: | + node --version + npm --version + - name: Install dependencies + run: | + npm install + npm install "node-api-headers@${{ matrix.node-api-headers-version }}" + - name: npm test + run: | + export NAPI_VERSION=${{ matrix.api_version }} + if [ "${{ matrix.compiler }}" = "gcc" ]; then + export CC="gcc" CXX="g++" + fi + if [ "${{ matrix.compiler }}" = "clang" ]; then + export CC="clang" CXX="clang++" + fi + echo "CC=\"$CC\" CXX=\"$CXX\"" + echo "$CC --version" + $CC --version + echo "$CXX --version" + $CXX --version + export CFLAGS="$CFLAGS -O3 --coverage" LDFLAGS="$LDFLAGS --coverage" + export use_node_api_headers=true + echo "CFLAGS=\"$CFLAGS\" LDFLAGS=\"$LDFLAGS\"" + npm run pretest -- --verbose diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..a6f09719a --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,51 @@ +name: release-please + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + id-token: write # Required for OIDC + contents: read + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + permissions: + contents: write + pull-requests: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + npm-publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 # npm >= 11.5.1 + registry-url: 'https://registry.npmjs.org' + - run: npm publish --provenance --access public diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 000000000..d902982dc --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,76 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: ["main"] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - name: "Checkout code" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + with: + sarif_file: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b4e77516b..7554cf154 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,16 +3,27 @@ on: schedule: - cron: "0 0 * * *" +permissions: + contents: read + jobs: stale: + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.' stale-issue-label: 'stale' - exempt-issue-label: 'never stale' + exempt-issue-labels: 'never-stale' days-before-stale: 90 days-before-close: 30 diff --git a/.gitignore b/.gitignore index c5b8bc871..a154db646 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,20 @@ /benchmark/build /benchmark/src /test/addon_build/addons +/test/require_basic_finalizers/addons +/.vscode + +# ignore node-gyp generated files outside its build directory +/test/*.Makefile +/test/*.mk + +# ignore node-gyp generated Visual Studio files +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.vsidx +*.sln +*.suo +/test/.vs/ +/test/Release/ +/test/Debug/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index feb7955cb..000000000 --- a/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -/test/ -.npmignore diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..f51a1eb82 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "8.9.1" +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 76224c508..000000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: c++ -compiler: - - clang - - gcc -# For Linux, use an Ubuntu 14 image -dist: trusty -os: - - linux - - osx -env: - global: - # https://github.com/jasongin/nvs/blob/master/doc/CI.md - - NVS_VERSION=1.4.2 - matrix: - - NODEJS_VERSION=node/10 - - NODEJS_VERSION=node/12 - - NODEJS_VERSION=node/14 - - NODEJS_VERSION=nightly -matrix: - fast_finish: true - allow_failures: - - env: NODEJS_VERSION=nightly -cache: - directories: - - node_modules - - $HOME/.npm -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.9 -before_install: - # coveralls - - pip2 install --user cpp-coveralls - # compilers - - if [ "$CXX" = "g++" -a "$TRAVIS_OS_NAME" = "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" AR="gcc-ar-4.9" RANLIB="gcc-ranlib-4.9" NM="gcc-nm-4.9" ; fi - - if [ "$CXX" = "clang++" ]; then export NPMOPT=--clang=1 ; fi - - export CFLAGS="$CFLAGS -O3 --coverage" LDFLAGS="$LDFLAGS --coverage" - - echo "CFLAGS=\"$CFLAGS\" LDFLAGS=\"$LDFLAGS\"" - # nvs - - git clone --branch v$NVS_VERSION --depth 1 https://github.com/jasongin/nvs ~/.nvs - - . ~/.nvs/nvs.sh - - nvs --version - # node.js - - nvs add $NODEJS_VERSION - - nvs use $NODEJS_VERSION - - node --version - - npm --version -install: - - npm install $NPMOPT -script: - # Travis CI sets NVM_NODEJS_ORG_MIRROR, but it makes node-gyp fail to download headers for nightly builds. - - unset NVM_NODEJS_ORG_MIRROR - - - npm test -after_success: - - cpp-coveralls --gcov-options '\-lp' --build-root test/build --exclude test diff --git a/CHANGELOG.md b/CHANGELOG.md index e77f4109e..466eca1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,698 @@ # node-addon-api Changelog +## [8.9.1](https://github.com/nodejs/node-addon-api/compare/v8.9.0...v8.9.1) (2026-07-31) + + +### Bug Fixes + +* fix vs2026 ICE compatibility ([#1739](https://github.com/nodejs/node-addon-api/issues/1739)) ([7223518](https://github.com/nodejs/node-addon-api/commit/722351807e21eaada1df16de1e959006c907a031)) + +## [8.9.0](https://github.com/nodejs/node-addon-api/compare/v8.8.0...v8.9.0) (2026-05-24) + + +### Features + +* add support for SharedArrayBuffer in TypedArray and TypedArrayOf<T> ([#1731](https://github.com/nodejs/node-addon-api/issues/1731)) ([00b95ef](https://github.com/nodejs/node-addon-api/commit/00b95efea6522980e9661a729a59b926ecf5c6b6)) + +## [8.8.0](https://github.com/nodejs/node-addon-api/compare/v8.7.0...v8.8.0) (2026-05-13) + + +### Features + +* add std::string_view overload for Symbol::For ([#1722](https://github.com/nodejs/node-addon-api/issues/1722)) ([f65113b](https://github.com/nodejs/node-addon-api/commit/f65113b6ce54271b0a26f97fc624b5574b64a048)) +* add String::New overload for string_view ([#1706](https://github.com/nodejs/node-addon-api/issues/1706)) ([0add130](https://github.com/nodejs/node-addon-api/commit/0add1306f60b81432da94d13683aa0b06aa52925)) + +## [8.7.0](https://github.com/nodejs/node-addon-api/compare/v8.6.0...v8.7.0) (2026-03-23) + + +### Features + +* add Date::New overload for a std::chrono::system_clock::time_point ([#1705](https://github.com/nodejs/node-addon-api/issues/1705)) ([7fb063d](https://github.com/nodejs/node-addon-api/commit/7fb063d95ff5ef816d1616f9acf4afac854cfd4c)) +* add Object::GetPrototype and Object::SetPrototype ([#1715](https://github.com/nodejs/node-addon-api/issues/1715)) ([967bbd5](https://github.com/nodejs/node-addon-api/commit/967bbd5911c7e90b428d4769b9ab1b1a0cee4451)), closes [#1691](https://github.com/nodejs/node-addon-api/issues/1691) +* add support for SharedArrayBuffer in DataViews ([#1714](https://github.com/nodejs/node-addon-api/issues/1714)) ([7b8d69e](https://github.com/nodejs/node-addon-api/commit/7b8d69e0ba912291aea0b337f7f1814b1032f7f0)) + + +### Bug Fixes + +* add missing const to ObjectReference::Set string parameter ([#1713](https://github.com/nodejs/node-addon-api/issues/1713)) ([845ba8e](https://github.com/nodejs/node-addon-api/commit/845ba8e4b0888ca20ed3f7c95f9d461cbce338c5)) +* fix -Wextra-semi ([#1718](https://github.com/nodejs/node-addon-api/issues/1718)) ([7fef973](https://github.com/nodejs/node-addon-api/commit/7fef9739166ebb89263459e9f4c3363678cd6367)) + +## [8.6.0](https://github.com/nodejs/node-addon-api/compare/v8.5.0...v8.6.0) (2026-01-30) + + +### Features + +* add SharedArrayBuffer ([#1688](https://github.com/nodejs/node-addon-api/issues/1688)) ([220bee2](https://github.com/nodejs/node-addon-api/commit/220bee244fae2e36405bf2bda33cb3985a846912)) +* silence a legitimate vfptr sanitizer warning that is on by default in Android NDK 29 ([#1692](https://github.com/nodejs/node-addon-api/issues/1692)) ([46673f4](https://github.com/nodejs/node-addon-api/commit/46673f403adf799cc73419427dd3cf166badff22)) + +## [8.5.0](https://github.com/nodejs/node-addon-api/compare/v8.4.0...v8.5.0) (2025-07-04) + + +### Features + +* add Then and Catch methods to Promise ([#1668](https://github.com/nodejs/node-addon-api/issues/1668)) ([ab3e5fe](https://github.com/nodejs/node-addon-api/commit/ab3e5fe59570cbb5ed7cc9891b3f25fe373f028f)) + +## [8.4.0](https://github.com/nodejs/node-addon-api/compare/v8.3.1...v8.4.0) (2025-06-11) + + +### Features + +* add sugar method for PropertyLValue ([#1651](https://github.com/nodejs/node-addon-api/issues/1651)) ([#1655](https://github.com/nodejs/node-addon-api/issues/1655)) ([1e57a0a](https://github.com/nodejs/node-addon-api/commit/1e57a0ae82786c320c784ec6b67f357c85733132)) + +## [8.3.1](https://github.com/nodejs/node-addon-api/compare/v8.3.0...v8.3.1) (2025-02-18) + + +### Bug Fixes + +* add missing `stdexcept` include to test ([#1634](https://github.com/nodejs/node-addon-api/issues/1634)) ([14c1a4f](https://github.com/nodejs/node-addon-api/commit/14c1a4f28278c5b02d0ea910061aad4312bb701e)) +* node-api version 10 support ([#1641](https://github.com/nodejs/node-addon-api/issues/1641)) ([932ad15](https://github.com/nodejs/node-addon-api/commit/932ad1503f7a3402716178a91879b5ab850a61b0)) + +## [8.3.0](https://github.com/nodejs/node-addon-api/compare/v8.2.2...v8.3.0) (2024-11-29) + + +### Features + +* allow catching all exceptions ([#1593](https://github.com/nodejs/node-addon-api/issues/1593)) ([c679f6f](https://github.com/nodejs/node-addon-api/commit/c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b)) + +## [8.2.2](https://github.com/nodejs/node-addon-api/compare/v8.2.1...v8.2.2) (2024-11-07) + + +### Bug Fixes + +* mark external memory and version APIs as basic ([#1597](https://github.com/nodejs/node-addon-api/issues/1597)) ([78da4fa](https://github.com/nodejs/node-addon-api/commit/78da4fa2251af1e4de16efac94d92388f117ae6e)) +* missing napi_delete_reference on ObjectWrap ref ([#1607](https://github.com/nodejs/node-addon-api/issues/1607)) ([98aae33](https://github.com/nodejs/node-addon-api/commit/98aae3343c3af36b4befd6b67c4cb19ba49b8d20)) + +## [8.2.1](https://github.com/nodejs/node-addon-api/compare/v8.2.0...v8.2.1) (2024-10-09) + + +### Bug Fixes + +* failed type cast checks in Symbol::WellKnown ([#1581](https://github.com/nodejs/node-addon-api/issues/1581)) ([d8523a7](https://github.com/nodejs/node-addon-api/commit/d8523a708030a0a3abb9d7832051c70e2dafac3d)) +* missing node_api_nogc_env definition ([#1585](https://github.com/nodejs/node-addon-api/issues/1585)) ([6ba3891](https://github.com/nodejs/node-addon-api/commit/6ba3891954d8b56215d133e54a86cb621e476b9e)) + +## [8.2.0](https://github.com/nodejs/node-addon-api/compare/v8.1.0...v8.2.0) (2024-09-19) + + +### Features + +* add support for nogc types via `BasicEnv` ([#1514](https://github.com/nodejs/node-addon-api/issues/1514)) ([b4aeecb](https://github.com/nodejs/node-addon-api/commit/b4aeecb046480eeaaf1c578a140f71ac0e77094f)) +* add support for requiring basic finalizers ([#1568](https://github.com/nodejs/node-addon-api/issues/1568)) ([7bcb826](https://github.com/nodejs/node-addon-api/commit/7bcb826aa4323f450b3c58f9c7fb34243ff13f77)) + + +### Bug Fixes + +* call base basic finalizer if none defined ([#1574](https://github.com/nodejs/node-addon-api/issues/1574)) ([294a43f](https://github.com/nodejs/node-addon-api/commit/294a43f8c6a4c79b3295a8f1b83d4782d44cfe74)) + +## [8.1.0](https://github.com/nodejs/node-addon-api/compare/node-addon-api-v8.0.0...node-addon-api-v8.1.0) (2024-07-05) + + +### Features + +* Expose version property in public API ([#1479](https://github.com/nodejs/node-addon-api/issues/1479)) ([23bb42b](https://github.com/nodejs/node-addon-api/commit/23bb42b5e47630c9082dddbabea555626571926e)) +* improve messages on CheckCast ([#1507](https://github.com/nodejs/node-addon-api/issues/1507)) ([bf49519](https://github.com/nodejs/node-addon-api/commit/bf49519a4ce08ee5320327c9a0199cd89d5b87b3)) + + +### Bug Fixes + +* fix compilation for Visual Studio 2022 ([#1492](https://github.com/nodejs/node-addon-api/issues/1492)) ([e011720](https://github.com/nodejs/node-addon-api/commit/e011720010af26ed66638ceac822e5f1c5e43cde)) +* restore ability to run under NAPI_EXPERIMENTAL ([#1409](https://github.com/nodejs/node-addon-api/issues/1409)) ([40bcb09](https://github.com/nodejs/node-addon-api/commit/40bcb09e6b82e7a1164cb3de56cb503d9b5a3d37)) + +## 2024-03-01 Version 8.0.0, @legendecas + +### Notable changes + +- Support for Node.js v16.x is no longer maintained. + +### Commits + +* \[[`df2147a2b6`](https://github.com/nodejs/node-addon-api/commit/df2147a2b6)] - build(deps): bump github/codeql-action from 3.24.3 to 3.24.5 (dependabot\[bot]) [#1455](https://github.com/nodejs/node-addon-api/pull/1455) +* \[[`eb4fa9b55a`](https://github.com/nodejs/node-addon-api/commit/eb4fa9b55a)] - build(deps): bump actions/dependency-review-action from 4.1.0 to 4.1.3 (dependabot\[bot]) [#1452](https://github.com/nodejs/node-addon-api/pull/1452) +* \[[`f85e8146bb`](https://github.com/nodejs/node-addon-api/commit/f85e8146bb)] - build(deps): bump github/codeql-action from 3.23.2 to 3.24.3 (dependabot\[bot]) [#1448](https://github.com/nodejs/node-addon-api/pull/1448) +* \[[`b84deb0d2f`](https://github.com/nodejs/node-addon-api/commit/b84deb0d2f)] - build(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.0 (dependabot\[bot]) [#1447](https://github.com/nodejs/node-addon-api/pull/1447) +* \[[`7dcee380cd`](https://github.com/nodejs/node-addon-api/commit/7dcee380cd)] - build(deps): bump actions/setup-node from 4.0.1 to 4.0.2 (dependabot\[bot]) [#1444](https://github.com/nodejs/node-addon-api/pull/1444) +* \[[`a727b629fe`](https://github.com/nodejs/node-addon-api/commit/a727b629fe)] - build(deps): bump actions/upload-artifact from 4.3.0 to 4.3.1 (dependabot\[bot]) [#1443](https://github.com/nodejs/node-addon-api/pull/1443) +* \[[`ea712094e3`](https://github.com/nodejs/node-addon-api/commit/ea712094e3)] - build(deps): bump step-security/harden-runner from 2.6.1 to 2.7.0 (dependabot\[bot]) [#1440](https://github.com/nodejs/node-addon-api/pull/1440) +* \[[`898e5006a5`](https://github.com/nodejs/node-addon-api/commit/898e5006a5)] - build(deps): bump github/codeql-action from 3.23.1 to 3.23.2 (dependabot\[bot]) [#1439](https://github.com/nodejs/node-addon-api/pull/1439) +* \[[`66e6e0e4b6`](https://github.com/nodejs/node-addon-api/commit/66e6e0e4b6)] - build(deps): bump actions/upload-artifact from 4.0.0 to 4.3.0 (dependabot\[bot]) [#1438](https://github.com/nodejs/node-addon-api/pull/1438) +* \[[`f1ca4ccd7f`](https://github.com/nodejs/node-addon-api/commit/f1ca4ccd7f)] - build(deps): bump actions/dependency-review-action from 3.1.5 to 4.0.0 (dependabot\[bot]) [#1433](https://github.com/nodejs/node-addon-api/pull/1433) +* \[[`c58112d52e`](https://github.com/nodejs/node-addon-api/commit/c58112d52e)] - build(deps): bump github/codeql-action from 3.23.0 to 3.23.1 (dependabot\[bot]) [#1430](https://github.com/nodejs/node-addon-api/pull/1430) +* \[[`f1b9c0bc24`](https://github.com/nodejs/node-addon-api/commit/f1b9c0bc24)] - **chore**: remove v16.x regular CI runs (Chengzhong Wu) [#1437](https://github.com/nodejs/node-addon-api/pull/1437) +* \[[`c6561d90d6`](https://github.com/nodejs/node-addon-api/commit/c6561d90d6)] - **chore**: reduce dependabot noise (Chengzhong Wu) [#1436](https://github.com/nodejs/node-addon-api/pull/1436) +* \[[`42931eeba6`](https://github.com/nodejs/node-addon-api/commit/42931eeba6)] - **doc**: reorganize readme (Chengzhong Wu) [#1441](https://github.com/nodejs/node-addon-api/pull/1441) +* \[[`3b9f3db14e`](https://github.com/nodejs/node-addon-api/commit/3b9f3db14e)] - **doc**: update changelog maker commands (Chengzhong Wu) [#1431](https://github.com/nodejs/node-addon-api/pull/1431) +* \[[`034c039298`](https://github.com/nodejs/node-addon-api/commit/034c039298)] - **test**: heed npm\_config\_debug (Gabriel Schulhof) [#1445](https://github.com/nodejs/node-addon-api/pull/1445) + +## 2024-01-18 Version 7.1.0, @legendecas + +### Notable changes + +#### API + +- Add Env::GetModuleFileName +- Add SyntaxError +- Allow NAPI\_VERSION env var and templatize AttachData callback +- Add common gyp dependency targets. + +### Commits + +* \[[`864fed488c`](https://github.com/nodejs/node-addon-api/commit/864fed488c)] - build(deps): bump github/codeql-action from 3.22.12 to 3.23.0 (dependabot\[bot]) [#1428](https://github.com/nodejs/node-addon-api/pull/1428) +* \[[`81a8d43130`](https://github.com/nodejs/node-addon-api/commit/81a8d43130)] - build(deps): bump actions/dependency-review-action from 3.1.4 to 3.1.5 (dependabot\[bot]) [#1427](https://github.com/nodejs/node-addon-api/pull/1427) +* \[[`e20088941b`](https://github.com/nodejs/node-addon-api/commit/e20088941b)] - build(deps): bump github/codeql-action from 3.22.11 to 3.22.12 (dependabot\[bot]) [#1426](https://github.com/nodejs/node-addon-api/pull/1426) +* \[[`76c7b12e4e`](https://github.com/nodejs/node-addon-api/commit/76c7b12e4e)] - build(deps): bump actions/setup-node from 4.0.0 to 4.0.1 (dependabot\[bot]) [#1425](https://github.com/nodejs/node-addon-api/pull/1425) +* \[[`cd58edde1d`](https://github.com/nodejs/node-addon-api/commit/cd58edde1d)] - build(deps): bump actions/upload-artifact from 3.1.3 to 4.0.0 (dependabot\[bot]) [#1424](https://github.com/nodejs/node-addon-api/pull/1424) +* \[[`0fd1b9e0e1`](https://github.com/nodejs/node-addon-api/commit/0fd1b9e0e1)] - build(deps): bump github/codeql-action from 2.22.8 to 3.22.11 (dependabot\[bot]) [#1423](https://github.com/nodejs/node-addon-api/pull/1423) +* \[[`c181b19d68`](https://github.com/nodejs/node-addon-api/commit/c181b19d68)] - build(deps): bump actions/stale from 8.0.0 to 9.0.0 (dependabot\[bot]) [#1418](https://github.com/nodejs/node-addon-api/pull/1418) +* \[[`6fa67791a1`](https://github.com/nodejs/node-addon-api/commit/6fa67791a1)] - build(deps): bump actions/setup-python from 4.7.1 to 5.0.0 (dependabot\[bot]) [#1417](https://github.com/nodejs/node-addon-api/pull/1417) +* \[[`1fff346fa6`](https://github.com/nodejs/node-addon-api/commit/1fff346fa6)] - build(deps): bump actions/dependency-review-action from 3.1.3 to 3.1.4 (dependabot\[bot]) [#1415](https://github.com/nodejs/node-addon-api/pull/1415) +* \[[`ecb9690fe5`](https://github.com/nodejs/node-addon-api/commit/ecb9690fe5)] - build(deps): bump github/codeql-action from 2.22.7 to 2.22.8 (dependabot\[bot]) [#1414](https://github.com/nodejs/node-addon-api/pull/1414) +* \[[`969547b871`](https://github.com/nodejs/node-addon-api/commit/969547b871)] - build(deps): bump github/codeql-action from 2.22.5 to 2.22.7 (dependabot\[bot]) [#1413](https://github.com/nodejs/node-addon-api/pull/1413) +* \[[`183d1522a9`](https://github.com/nodejs/node-addon-api/commit/183d1522a9)] - build(deps): bump step-security/harden-runner from 2.6.0 to 2.6.1 (dependabot\[bot]) [#1412](https://github.com/nodejs/node-addon-api/pull/1412) +* \[[`25f977724a`](https://github.com/nodejs/node-addon-api/commit/25f977724a)] - build(deps): bump actions/dependency-review-action from 3.1.0 to 3.1.3 (dependabot\[bot]) [#1410](https://github.com/nodejs/node-addon-api/pull/1410) +* \[[`f6d125a407`](https://github.com/nodejs/node-addon-api/commit/f6d125a407)] - build(deps): bump actions/setup-python from 4.7.0 to 4.7.1 (dependabot\[bot]) [#1406](https://github.com/nodejs/node-addon-api/pull/1406) +* \[[`ce78a39ec7`](https://github.com/nodejs/node-addon-api/commit/ce78a39ec7)] - build(deps): bump github/codeql-action from 2.22.4 to 2.22.5 (dependabot\[bot]) [#1400](https://github.com/nodejs/node-addon-api/pull/1400) +* \[[`dc211ebb48`](https://github.com/nodejs/node-addon-api/commit/dc211ebb48)] - build(deps): bump actions/setup-node from 3.8.1 to 4.0.0 (dependabot\[bot]) [#1398](https://github.com/nodejs/node-addon-api/pull/1398) +* \[[`cab559e3bd`](https://github.com/nodejs/node-addon-api/commit/cab559e3bd)] - build(deps): bump ossf/scorecard-action from 2.3.0 to 2.3.1 (dependabot\[bot]) [#1397](https://github.com/nodejs/node-addon-api/pull/1397) +* \[[`f71ff5582d`](https://github.com/nodejs/node-addon-api/commit/f71ff5582d)] - build(deps): bump github/codeql-action from 2.22.3 to 2.22.4 (dependabot\[bot]) [#1396](https://github.com/nodejs/node-addon-api/pull/1396) +* \[[`21c1d08680`](https://github.com/nodejs/node-addon-api/commit/21c1d08680)] - build(deps): bump actions/checkout from 4.1.0 to 4.1.1 (dependabot\[bot]) [#1394](https://github.com/nodejs/node-addon-api/pull/1394) +* \[[`e4eec0939c`](https://github.com/nodejs/node-addon-api/commit/e4eec0939c)] - build(deps): bump github/codeql-action from 2.21.9 to 2.22.3 (dependabot\[bot]) [#1393](https://github.com/nodejs/node-addon-api/pull/1393) +* \[[`94f3459474`](https://github.com/nodejs/node-addon-api/commit/94f3459474)] - build(deps): bump ossf/scorecard-action from 2.2.0 to 2.3.0 (dependabot\[bot]) [#1388](https://github.com/nodejs/node-addon-api/pull/1388) +* \[[`90a741ef10`](https://github.com/nodejs/node-addon-api/commit/90a741ef10)] - build(deps): bump step-security/harden-runner from 2.5.1 to 2.6.0 (dependabot\[bot]) [#1386](https://github.com/nodejs/node-addon-api/pull/1386) +* \[[`7e1aa06132`](https://github.com/nodejs/node-addon-api/commit/7e1aa06132)] - Update LICENSE.md (Michael Dawson) [#1385](https://github.com/nodejs/node-addon-api/pull/1385) +* \[[`0a0612362e`](https://github.com/nodejs/node-addon-api/commit/0a0612362e)] - build(deps): bump github/codeql-action from 2.21.7 to 2.21.9 (dependabot\[bot]) [#1384](https://github.com/nodejs/node-addon-api/pull/1384) +* \[[`47bd430da2`](https://github.com/nodejs/node-addon-api/commit/47bd430da2)] - build(deps): bump actions/checkout from 4.0.0 to 4.1.0 (dependabot\[bot]) [#1383](https://github.com/nodejs/node-addon-api/pull/1383) +* \[[`b3f7f73cb9`](https://github.com/nodejs/node-addon-api/commit/b3f7f73cb9)] - build(deps): bump actions/dependency-review-action from 3.0.8 to 3.1.0 (dependabot\[bot]) [#1377](https://github.com/nodejs/node-addon-api/pull/1377) +* \[[`12c1655387`](https://github.com/nodejs/node-addon-api/commit/12c1655387)] - build(deps): bump github/codeql-action from 2.21.6 to 2.21.7 (dependabot\[bot]) [#1380](https://github.com/nodejs/node-addon-api/pull/1380) +* \[[`6abed318e4`](https://github.com/nodejs/node-addon-api/commit/6abed318e4)] - build(deps): bump github/codeql-action from 2.21.5 to 2.21.6 (dependabot\[bot]) [#1378](https://github.com/nodejs/node-addon-api/pull/1378) +* \[[`89eda59930`](https://github.com/nodejs/node-addon-api/commit/89eda59930)] - build(deps): bump actions/upload-artifact from 3.1.2 to 3.1.3 (dependabot\[bot]) [#1376](https://github.com/nodejs/node-addon-api/pull/1376) +* \[[`90870dbffa`](https://github.com/nodejs/node-addon-api/commit/90870dbffa)] - build(deps): bump actions/checkout from 3.6.0 to 4.0.0 (dependabot\[bot]) [#1375](https://github.com/nodejs/node-addon-api/pull/1375) +* \[[`b860793eff`](https://github.com/nodejs/node-addon-api/commit/b860793eff)] - build(deps): bump github/codeql-action from 2.21.2 to 2.21.5 (dependabot\[bot]) [#1372](https://github.com/nodejs/node-addon-api/pull/1372) +* \[[`f9b9974b4a`](https://github.com/nodejs/node-addon-api/commit/f9b9974b4a)] - build(deps): bump actions/checkout from 3.5.3 to 3.6.0 (dependabot\[bot]) [#1371](https://github.com/nodejs/node-addon-api/pull/1371) +* \[[`9596e3de2d`](https://github.com/nodejs/node-addon-api/commit/9596e3de2d)] - build(deps): bump actions/setup-node from 3.7.0 to 3.8.1 (dependabot\[bot]) [#1370](https://github.com/nodejs/node-addon-api/pull/1370) +* \[[`e969210747`](https://github.com/nodejs/node-addon-api/commit/e969210747)] - build(deps): bump actions/dependency-review-action from 3.0.6 to 3.0.8 (dependabot\[bot]) [#1368](https://github.com/nodejs/node-addon-api/pull/1368) +* \[[`13ef96a5a9`](https://github.com/nodejs/node-addon-api/commit/13ef96a5a9)] - build(deps): bump step-security/harden-runner from 2.5.0 to 2.5.1 (dependabot\[bot]) [#1364](https://github.com/nodejs/node-addon-api/pull/1364) +* \[[`9776d148b3`](https://github.com/nodejs/node-addon-api/commit/9776d148b3)] - build(deps): bump github/codeql-action from 2.21.1 to 2.21.2 (dependabot\[bot]) [#1358](https://github.com/nodejs/node-addon-api/pull/1358) +* \[[`59dc6be097`](https://github.com/nodejs/node-addon-api/commit/59dc6be097)] - build(deps): bump github/codeql-action from 2.21.0 to 2.21.1 (dependabot\[bot]) [#1357](https://github.com/nodejs/node-addon-api/pull/1357) +* \[[`5e72796cd5`](https://github.com/nodejs/node-addon-api/commit/5e72796cd5)] - build(deps): bump step-security/harden-runner from 2.4.1 to 2.5.0 (dependabot\[bot]) [#1356](https://github.com/nodejs/node-addon-api/pull/1356) +* \[[`4e62db45e4`](https://github.com/nodejs/node-addon-api/commit/4e62db45e4)] - build(deps): bump github/codeql-action from 2.20.3 to 2.21.0 (dependabot\[bot]) [#1353](https://github.com/nodejs/node-addon-api/pull/1353) +* \[[`0c093a33e8`](https://github.com/nodejs/node-addon-api/commit/0c093a33e8)] - build(deps): bump github/codeql-action from 2.20.1 to 2.20.3 (dependabot\[bot]) [#1349](https://github.com/nodejs/node-addon-api/pull/1349) +* \[[`5523b2d3fa`](https://github.com/nodejs/node-addon-api/commit/5523b2d3fa)] - build(deps): bump actions/setup-node from 3.6.0 to 3.7.0 (dependabot\[bot]) [#1348](https://github.com/nodejs/node-addon-api/pull/1348) +* \[[`afa494ef7f`](https://github.com/nodejs/node-addon-api/commit/afa494ef7f)] - Add Node.js version restrictions (Ingo Fischer) [#1340](https://github.com/nodejs/node-addon-api/pull/1340) +* \[[`ac4c87f660`](https://github.com/nodejs/node-addon-api/commit/ac4c87f660)] - build(deps): bump ossf/scorecard-action from 2.0.6 to 2.2.0 (dependabot\[bot]) [#1344](https://github.com/nodejs/node-addon-api/pull/1344) +* \[[`47aeb6689d`](https://github.com/nodejs/node-addon-api/commit/47aeb6689d)] - build(deps): bump github/codeql-action from 2.2.12 to 2.20.1 (dependabot\[bot]) [#1343](https://github.com/nodejs/node-addon-api/pull/1343) +* \[[`bd45a8fffc`](https://github.com/nodejs/node-addon-api/commit/bd45a8fffc)] - build(deps): bump step-security/harden-runner from 2.3.0 to 2.4.1 (dependabot\[bot]) [#1342](https://github.com/nodejs/node-addon-api/pull/1342) +* \[[`343a1e1708`](https://github.com/nodejs/node-addon-api/commit/343a1e1708)] - build(deps-dev): bump fs-extra from 9.1.0 to 11.1.1 (dependabot\[bot]) [#1335](https://github.com/nodejs/node-addon-api/pull/1335) +* \[[`4168c10182`](https://github.com/nodejs/node-addon-api/commit/4168c10182)] - build(deps): bump actions/stale from 5.2.1 to 8.0.0 (dependabot\[bot]) [#1333](https://github.com/nodejs/node-addon-api/pull/1333) +* \[[`1c182abd1f`](https://github.com/nodejs/node-addon-api/commit/1c182abd1f)] - build(deps): bump actions/dependency-review-action from 2.5.1 to 3.0.6 (dependabot\[bot]) [#1331](https://github.com/nodejs/node-addon-api/pull/1331) +* \[[`717a61931d`](https://github.com/nodejs/node-addon-api/commit/717a61931d)] - build(deps): bump actions/checkout from 3.5.2 to 3.5.3 (dependabot\[bot]) [#1329](https://github.com/nodejs/node-addon-api/pull/1329) +* \[[`d605d62c89`](https://github.com/nodejs/node-addon-api/commit/d605d62c89)] - **chore**: lock python version in actions (Chengzhong Wu) [#1403](https://github.com/nodejs/node-addon-api/pull/1403) +* \[[`734e3f2509`](https://github.com/nodejs/node-addon-api/commit/734e3f2509)] - **doc**: fix rendering of code blocks in list (Tobias Nießen) [#1401](https://github.com/nodejs/node-addon-api/pull/1401) +* \[[`dfdf6eb6e6`](https://github.com/nodejs/node-addon-api/commit/dfdf6eb6e6)] - **doc**: add missing title IsBigInt (Marx) [#1352](https://github.com/nodejs/node-addon-api/pull/1352) +* \[[`8850997f38`](https://github.com/nodejs/node-addon-api/commit/8850997f38)] - **doc**: fix typo AsyncProgressWorker::ExecutionProgress (JerryZhongJ) [#1350](https://github.com/nodejs/node-addon-api/pull/1350) +* \[[`8192a471a1`](https://github.com/nodejs/node-addon-api/commit/8192a471a1)] - **docs**: fixed Broken Links (Ömer AKGÜL) [#1405](https://github.com/nodejs/node-addon-api/pull/1405) +* \[[`16a18c047a`](https://github.com/nodejs/node-addon-api/commit/16a18c047a)] - **fix**: handle c++ exception in TSFN callback (Chengzhong Wu) [#1345](https://github.com/nodejs/node-addon-api/pull/1345) +* \[[`ab14347080`](https://github.com/nodejs/node-addon-api/commit/ab14347080)] - **gyp**: add common targets (Chengzhong Wu) [#1389](https://github.com/nodejs/node-addon-api/pull/1389) +* \[[`fa3518bc08`](https://github.com/nodejs/node-addon-api/commit/fa3518bc08)] - **src**: remove duplicate buffer info calls (Chengzhong Wu) [#1354](https://github.com/nodejs/node-addon-api/pull/1354) +* \[[`b83e453e6e`](https://github.com/nodejs/node-addon-api/commit/b83e453e6e)] - **src**: add Env::GetModuleFileName (Kevin Eady) [#1327](https://github.com/nodejs/node-addon-api/pull/1327) +* \[[`d9828c6264`](https://github.com/nodejs/node-addon-api/commit/d9828c6264)] - **src**: add SyntaxError (Kevin Eady) [#1326](https://github.com/nodejs/node-addon-api/pull/1326) +* \[[`c52e764bb2`](https://github.com/nodejs/node-addon-api/commit/c52e764bb2)] - **src,test,build**: allow NAPI\_VERSION env var and templatize AttachData callback (Gabriel Schulhof) [#1399](https://github.com/nodejs/node-addon-api/pull/1399) +* \[[`8f028d630a`](https://github.com/nodejs/node-addon-api/commit/8f028d630a)] - **test**: remove experimental flag from bigint (Gabriel Schulhof) [#1395](https://github.com/nodejs/node-addon-api/pull/1395) +* \[[`414be9e000`](https://github.com/nodejs/node-addon-api/commit/414be9e000)] - **test**: run interfering tests in their own process (Gabriel Schulhof) [#1325](https://github.com/nodejs/node-addon-api/pull/1325) + +## 2023-06-13 Version 7.0.0, @KevinEady + +### Notable changes + +#### API + +- Drop support for Node.js v14.x and v19.x. +- Ensure native receiver exists when calling instance methods and properties. +- Fix issue when creating `Napi::Error` instances that wrap primitives values. + +#### TEST + +- Added tests for `Napi::AsyncProgressQueueWorker` class. +- Added tests for `Napi::AsyncProgressWorker` class. + +### Documentation + +- Added documentation for `Napi::Value::IsBigInt()`. + +### Commits + +* \[[`de5c899400`](https://github.com/nodejs/node-addon-api/commit/de5c899400)] - **doc,chore**: drop support for Node.js v14, v19 (Kevin Eady) [#1324](https://github.com/nodejs/node-addon-api/pull/1324) +* \[[`3083b7f148`](https://github.com/nodejs/node-addon-api/commit/3083b7f148)] - \[StepSecurity] Apply security best practices (StepSecurity Bot) [#1308](https://github.com/nodejs/node-addon-api/pull/1308) +* \[[`a198e24a15`](https://github.com/nodejs/node-addon-api/commit/a198e24a15)] - \[Test] Add tests for async progress queue worker (Jack) [#1316](https://github.com/nodejs/node-addon-api/pull/1316) +* \[[`665f4aa845`](https://github.com/nodejs/node-addon-api/commit/665f4aa845)] - **doc**: add missing Value::IsBigInt (Kevin Eady) [#1319](https://github.com/nodejs/node-addon-api/pull/1319) +* \[[`358b2d3b4f`](https://github.com/nodejs/node-addon-api/commit/358b2d3b4f)] - **doc**: complete code curly braces in async\_worker.md (wanlu) [#1317](https://github.com/nodejs/node-addon-api/pull/1317) +* \[[`858942ce31`](https://github.com/nodejs/node-addon-api/commit/858942ce31)] - **src**: avoid calling into C++ with a null this (Caleb Hearon) [#1313](https://github.com/nodejs/node-addon-api/pull/1313) +* \[[`64f6515331`](https://github.com/nodejs/node-addon-api/commit/64f6515331)] - **src**: handle failure during error wrap of primitive (Gabriel Schulhof) [#1310](https://github.com/nodejs/node-addon-api/pull/1310) +* \[[`dfad6b45fe`](https://github.com/nodejs/node-addon-api/commit/dfad6b45fe)] - \[test] Add test coverage for AsyncProgressWorker (Jack) [#1307](https://github.com/nodejs/node-addon-api/pull/1307) +* \[[`0e34f22839`](https://github.com/nodejs/node-addon-api/commit/0e34f22839)] - **release**: v6.1.0. (Nicola Del Gobbo) + +## 2023-04-20 Version 6.1.0, @NickNaso + +### Notable changes + +#### API + +- Enforce type checks on `Napi::Value::As()`. +- Added `Napi::TypeTaggable` class. +- Defined `NAPI_HAS_THREADS` to make TSFN available on Emscripten. +- Defined `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` and +`Napi::Buffer::NewOrCopy()` to handle the support for external buffers. + +#### TEST + +- Added tests for `Napi::Reference` class. +- Added tests for copy/move semantics. +- Added tests for `Napi::RangeError` and `Napi::TypeError` class. +- Fixed inconsistent failure executing test suite. +- Added tests for `Napi::ObjectReference` class. +- Added tests for `Napi::ObjectWrap` class. + +### Documentation + +- Added documentation for `Napi::TypeTaggable`. +- Some minor fixes all over the documentation. + +### Commits + +- \[[`5adb896782`](https://github.com/nodejs/node-addon-api/commit/5adb896782)] - **src**: enforce type checks on Napi::Value::As() (#1281) (Chengzhong Wu) +- \[[`d9faac7ec2`](https://github.com/nodejs/node-addon-api/commit/d9faac7ec2)] - Fix exits/exists typo in docs for Env::AddCleanupHook() (#1306) (Mathias Stearn) +- \[[`164459ca03`](https://github.com/nodejs/node-addon-api/commit/164459ca03)] - **doc**: update class hierarchy for TypeTaggable (Gabriel Schulhof) [#1303](https://github.com/nodejs/node-addon-api/pull/1303) +- \[[`d01304437c`](https://github.com/nodejs/node-addon-api/commit/d01304437c)] - **src**: interject class TypeTaggable (Gabriel Schulhof) [#1298](https://github.com/nodejs/node-addon-api/pull/1298) +- \[[`d4942ccd4f`](https://github.com/nodejs/node-addon-api/commit/d4942ccd4f)] - **test**: Complete test coverage for Reference\ class (#1277) (Jack) +- \[[`a8ad7e7a7b`](https://github.com/nodejs/node-addon-api/commit/a8ad7e7a7b)] - **test**: Add tests for copy/move semantics (JckXia) [#1295](https://github.com/nodejs/node-addon-api/pull/1295) +- \[[`e484327344`](https://github.com/nodejs/node-addon-api/commit/e484327344)] - Add test coverage for typed and range err (#1280) (Jack) +- \[[`ebc7858593`](https://github.com/nodejs/node-addon-api/commit/ebc7858593)] - **test**: Update wait with a condition (#1297) (Jack) +- \[[`0b53d885f5`](https://github.com/nodejs/node-addon-api/commit/0b53d885f5)] - **src**: define `NAPI_HAS_THREADS` (toyobayashi) [#1283](https://github.com/nodejs/node-addon-api/pull/1283) +- \[[`464610babf`](https://github.com/nodejs/node-addon-api/commit/464610babf)] - **test**: complete objectRefs tests (JckXia) [#1274](https://github.com/nodejs/node-addon-api/pull/1274) +- \[[`b16c762a19`](https://github.com/nodejs/node-addon-api/commit/b16c762a19)] - **src**: handle no support for external buffers (legendecas) [#1273](https://github.com/nodejs/node-addon-api/pull/1273) +- \[[`61b8e28720`](https://github.com/nodejs/node-addon-api/commit/61b8e28720)] - **test**: Add test covg for obj wrap (#1269) (Jack) + +## 2023-02-03 Version 6.0.0, @NickNaso + +### Notable changes + +#### API + +- Added `Napi::Object::TypeTag()` and `Napi::Object::CheckTypeTag()` methods. +- Made operator `napi_callback_info` explicit. + +#### TEST + +- Some minor fixes all over the test suite. +- Added tests related to `Napi::Object::TypeTag()` and `Napi::Object::CheckTypeTag()` methods. +- Added tests related to `Napi::CallbackScope`. +- Added tests related to `Napi::EscapableHandleScope`. +- Added tests related to `Napi::Maybe`. +- Added tests related to `Napi::ThreadSafeFuntion`. +- Changed some tests related to `Napi::AsyncWorker`. + +### Documentation + +- Added documentation for `Napi::Object::TypeTag()` and `Napi::Object::CheckTypeTag()` methods. +- Added documentation about how to run a specific unit test. + +### TOOL + +- Added `x86` architecture to the CI matrix. + +### Commits + +* \[[`e2726193f1`](https://github.com/nodejs/node-addon-api/commit/e2726193f1)] - **src**: remove AsyncWorker move and complete tests (JckXia) [#1266](https://github.com/nodejs/node-addon-api/pull/1266) +* \[[`ff969485ea`](https://github.com/nodejs/node-addon-api/commit/ff969485ea)] - **chore**: build node-addon-api against X86 (JckXia) [#1276](https://github.com/nodejs/node-addon-api/pull/1276) +* \[[`a70564cdfd`](https://github.com/nodejs/node-addon-api/commit/a70564cdfd)] - **test**: add cov for ThreadSafeFunction new overloads (JckXia) [#1251](https://github.com/nodejs/node-addon-api/pull/1251) +* \[[`53f7cf1d48`](https://github.com/nodejs/node-addon-api/commit/53f7cf1d48)] - **src**: make operator napi\_callback\_info explicit (Kevin Eady) [#1275](https://github.com/nodejs/node-addon-api/pull/1275) +* \[[`78b5a15533`](https://github.com/nodejs/node-addon-api/commit/78b5a15533)] - **test**: Add tests for ThreadSafeFunction's NonBlock function overloads (#1249) (Jack) +* \[[`fdc6263034`](https://github.com/nodejs/node-addon-api/commit/fdc6263034)] - **test**: Add test covg for Maybe\ (#1270) (Jack) +* \[[`35d9d669b3`](https://github.com/nodejs/node-addon-api/commit/35d9d669b3)] - **test**: add test covg for handle and escapehandle scopes (JckXia) [#1263](https://github.com/nodejs/node-addon-api/pull/1263) +* \[[`021313409e`](https://github.com/nodejs/node-addon-api/commit/021313409e)] - **test**: add unit test covg for callbackscopes (JckXia) [#1262](https://github.com/nodejs/node-addon-api/pull/1262) +* \[[`b11e4de2cf`](https://github.com/nodejs/node-addon-api/commit/b11e4de2cf)] - **src**: add Object::TypeTag, Object::CheckTypeTag (Kevin Eady) [#1261](https://github.com/nodejs/node-addon-api/pull/1261) + +## 2023-01-13 Version 5.1.0, @NickNaso + +### Notable changes + +#### API + +- Fixed memory leak in `Napi::AsyncProgressWorkerBase`. +- Added api to get `callback_info` from `Napi::CallBackInfo`. +- Fixed erros and warning in VS 2017. +- Made `Npi::Env::CleanupHook` public. +- Removed `Napi::TypedArray::unknown_array_type`. + +#### TEST + +- Some minor fixes all over the test suite. +- Added tests related to `Napi::Env`. +- Added tests related to `Napi::TypedArray`. +- Added tests related to `Napi::AsyncWorker`. +- Added tests related to `Napi::TypedThreadSafeFunction`. +- Added tests related to `Napi::Value`. +- Added test related to `Napi::Promise`. + +### Documentation + +- Some minor fixes all over the documentation. +- Added `Napi::HandleScope` example. +- Added documentation about how to run a specific unit test. + +### TOOL + +- Added Windows with VS 2022 and Node.JS 19.x to the CI matrix. +- Fixed stale workflow. +- Updated Node.js versions on CI component. +- Added condition for Window to find eslint. + +### Commits + +* \[[`79a446fb9c`](https://github.com/nodejs/node-addon-api/commit/79a446fb9c)] - Update contributors (#1265) (Kevin Eady) +* \[[`01c61690c6`](https://github.com/nodejs/node-addon-api/commit/01c61690c6)] - **src**: napi-inl: Fix a memory leak bug in `AsyncProgressWorkerBase` (Ammar Faizi) [#1264](https://github.com/nodejs/node-addon-api/pull/1264) +* \[[`55bd08ee26`](https://github.com/nodejs/node-addon-api/commit/55bd08ee26)] - **src**: api to get callback\_info from CallBackInfo (JckXia) [#1253](https://github.com/nodejs/node-addon-api/pull/1253) +* \[[`ad76256714`](https://github.com/nodejs/node-addon-api/commit/ad76256714)] - **test**: add tests related to env (JckXia) [#1254](https://github.com/nodejs/node-addon-api/pull/1254) +* \[[`5c3937365d`](https://github.com/nodejs/node-addon-api/commit/5c3937365d)] - **chore**: add Windows with VS 2022 and Node.JS 19.x to the CI matrix (#1252) (Vladimir Morozov) +* \[[`97736c93f4`](https://github.com/nodejs/node-addon-api/commit/97736c93f4)] - **src**: fix errors and warnings in VS 2017 (Vladimir Morozov) [#1245](https://github.com/nodejs/node-addon-api/pull/1245) +* \[[`ad7ff92c16`](https://github.com/nodejs/node-addon-api/commit/ad7ff92c16)] - **src**: refactor call js wrapper (#1242) (Jack) +* \[[`39267baf1b`](https://github.com/nodejs/node-addon-api/commit/39267baf1b)] - **src**: make CleanupHook public (Julian Mesa) [#1240](https://github.com/nodejs/node-addon-api/pull/1240) +* \[[`edf630cc79`](https://github.com/nodejs/node-addon-api/commit/edf630cc79)] - **src**: fix implementation of Signal (Kevin Eady) [#1216](https://github.com/nodejs/node-addon-api/pull/1216) +* \[[`de5a502f3c`](https://github.com/nodejs/node-addon-api/commit/de5a502f3c)] - **doc**: Napi::Error is caught (Nicola Del Gobbo) [#1241](https://github.com/nodejs/node-addon-api/pull/1241) +* \[[`10ad762807`](https://github.com/nodejs/node-addon-api/commit/10ad762807)] - **test**: removed the usage of default\_configuration. (Nicola Del Gobbo) [#1226](https://github.com/nodejs/node-addon-api/pull/1226) +* \[[`e9db2adef2`](https://github.com/nodejs/node-addon-api/commit/e9db2adef2)] - **test**: Add test coverage to TSFN::New() overloads (#1201) (Jack) +* \[[`c849ad3f6a`](https://github.com/nodejs/node-addon-api/commit/c849ad3f6a)] - **chore**: fix stale workflow (#1228) (Richard Lau) +* \[[`e408804ad8`](https://github.com/nodejs/node-addon-api/commit/e408804ad8)] - **test**: adding ref for threadsafefunctions (JckXia) [#1222](https://github.com/nodejs/node-addon-api/pull/1222) +* \[[`a8afb2d73c`](https://github.com/nodejs/node-addon-api/commit/a8afb2d73c)] - **src**: remove TypedArray::unknown\_array\_type (Kevin Eady) [#1209](https://github.com/nodejs/node-addon-api/pull/1209) +* \[[`257a52f823`](https://github.com/nodejs/node-addon-api/commit/257a52f823)] - **test**: Add test cased for failed task cancellations (#1214) (Jack) +* \[[`793268c59f`](https://github.com/nodejs/node-addon-api/commit/793268c59f)] - **test**: Add test case for canceling async worker tasks (#1202) (Jack) +* \[[`1331856ef1`](https://github.com/nodejs/node-addon-api/commit/1331856ef1)] - **doc**: add HandleScope example (#1210) (Kevin Eady) +* \[[`d5fc875e5d`](https://github.com/nodejs/node-addon-api/commit/d5fc875e5d)] - **test**: remove update to process.config (#1208) (Michael Dawson) +* \[[`30cd4a37f0`](https://github.com/nodejs/node-addon-api/commit/30cd4a37f0)] - **test**: add tests for .Data method (JckXia) [#1203](https://github.com/nodejs/node-addon-api/pull/1203) +* \[[`225ca35963`](https://github.com/nodejs/node-addon-api/commit/225ca35963)] - **test**: Add test coverage for "TSFN::Ref()" (#1196) (Jack) +* \[[`5a5a213985`](https://github.com/nodejs/node-addon-api/commit/5a5a213985)] - Update CI component versions (#1200) (Vladimir Morozov) +* \[[`fb27e72b0c`](https://github.com/nodejs/node-addon-api/commit/fb27e72b0c)] - **doc**: Update CONTRIBUTING.md (Saint Gabriel) [#1185](https://github.com/nodejs/node-addon-api/pull/1185) +* \[[`e9def3ed72`](https://github.com/nodejs/node-addon-api/commit/e9def3ed72)] - **doc**: Update Readme for filter conditions in unit tests (Deepak Rajamohan) [#1199](https://github.com/nodejs/node-addon-api/pull/1199) +* \[[`efd67876e1`](https://github.com/nodejs/node-addon-api/commit/efd67876e1)] - **doc**: updated npm script for focused tests (Peter Šándor) +* \[[`134961d853`](https://github.com/nodejs/node-addon-api/commit/134961d853)] - **test**: CallbackInfo NewTarget() basic coverage (#1048) (Peter Šándor) +* \[[`1dfd03bdd5`](https://github.com/nodejs/node-addon-api/commit/1dfd03bdd5)] - Update README.md (#1187) (Saint Gabriel) +* \[[`576128fd19`](https://github.com/nodejs/node-addon-api/commit/576128fd19)] - **doc**: fix typo in async\_operations.md (#1189) (Tobias Nießen) +* \[[`63d3c30ec1`](https://github.com/nodejs/node-addon-api/commit/63d3c30ec1)] - **test**: add tests for TypedArray (Dante Calderon) [#1179](https://github.com/nodejs/node-addon-api/pull/1179) +* \[[`358ac2f080`](https://github.com/nodejs/node-addon-api/commit/358ac2f080)] - Fix link to CMake.js documentation (#1180) (Kyle Kovacs) +* \[[`dc4f2bbe4a`](https://github.com/nodejs/node-addon-api/commit/dc4f2bbe4a)] - **test**: Add promise unit test (#1173) (Jenny) +* \[[`f3124ae0ed`](https://github.com/nodejs/node-addon-api/commit/f3124ae0ed)] - **doc**: fix broken `Napi::ThreadSafeFunction` link (#1172) (Feng Yu) +* \[[`10b440fe27`](https://github.com/nodejs/node-addon-api/commit/10b440fe27)] - **src**: reformat all code (Kevin Eady) [#1160](https://github.com/nodejs/node-addon-api/pull/1160) +* \[[`33e402971e`](https://github.com/nodejs/node-addon-api/commit/33e402971e)] - **test**: Add condition for window to find eslint (#1176) (Jack) +* \[[`d53843b83b`](https://github.com/nodejs/node-addon-api/commit/d53843b83b)] - **test**: add missing value tests (JckXia) [#1170](https://github.com/nodejs/node-addon-api/pull/1170) + +## 2022-05-02 Version 5.0.0, @NickNaso + +### Notable changes: + +#### API +- Marked methods of wrapper classes `const`. +- Enabled wrapping `Napi` namespace with custom namespace. +- Added an override to `Napi::Function::Call` to call it with a c-style array +of `Napi::Value`'s. +- Some other minor fixes. + +#### TEST + +- Improved the test framework. Added the possibility to run subsets of tests +more easily. +- Added test for `Napi::AsyncContext` class. +- Fixed ramdom failure on test for `Napi::ThreadSafeFunction` e +`Napi::TypedThreadSafeFunction` class. +- Fixed compilation problem on debian 8 system. +- Added test for `Napi::Object::Set()` method. + +### Documentation +- Added some clarifications for `Napi::ClassPropertyDescriptor`. +- Added clarification about weak reference for `Napi::ObjectWrap`. +- Some minor fixes all over the documentation. + +### TOOL + +- Fixed `eslint` configuration. +- Fixed CI configuration for Windows. +- Enabled pre-commit `ClangFormat` on Windows. + +### Commits + +* \[[`f32db917f3`](https://github.com/nodejs/node-addon-api/commit/f32db917f3)] - Add test coverage for async contexts (#1164) (Jack) +* \[[`24455f88af`](https://github.com/nodejs/node-addon-api/commit/24455f88af)] - **src**: check for tsfn in conditional\_variable wait (Kevin Eady) [#1168](https://github.com/nodejs/node-addon-api/pull/1168) +* \[[`40ed7ce409`](https://github.com/nodejs/node-addon-api/commit/40ed7ce409)] - **src**: fix regression introduced by #874 (Michael Dawson) +* \[[`9bea434326`](https://github.com/nodejs/node-addon-api/commit/9bea434326)] - **doc**: added some comments to ClassPropertyDescriptor. (#1149) (Nicola Del Gobbo) +* \[[`57c212e15f`](https://github.com/nodejs/node-addon-api/commit/57c212e15f)] - **buld**: Enable running pre-commit ClangFormat on Win (Vladimir Morozov) +* \[[`8c46a9501a`](https://github.com/nodejs/node-addon-api/commit/8c46a9501a)] - **doc**: clarify ObjectWrap weak ref behavior (#1155) (Alba Mendez) +* \[[`01274966d5`](https://github.com/nodejs/node-addon-api/commit/01274966d5)] - **build**: run Windows CI only on nondeprecated build configurations (#1152) (Darshan Sen) +* \[[`b8449e17e0`](https://github.com/nodejs/node-addon-api/commit/b8449e17e0)] - **src**: mark methods of wrapper classes const (Nikolai Vavilov) [#874](https://github.com/nodejs/node-addon-api/pull/874) +* \[[`5e2c1f24f8`](https://github.com/nodejs/node-addon-api/commit/5e2c1f24f8)] - **lint**: set sourceType to 'script' (#1141) (Anna Henningsen) +* \[[`da8af20152`](https://github.com/nodejs/node-addon-api/commit/da8af20152)] - **doc**: mention Napi::Env arg for Finalization callback (#1139) (extremeheat) +* \[[`5b51864a39`](https://github.com/nodejs/node-addon-api/commit/5b51864a39)] - **src**: enable wrapping Napi namespace with custom namespace (#1135) (Anna Henningsen) +* \[[`c54aeef5fd`](https://github.com/nodejs/node-addon-api/commit/c54aeef5fd)] - Add Function::Call Napi::Value override (#1026) (rgerd) +* \[[`e906b5a7ce`](https://github.com/nodejs/node-addon-api/commit/e906b5a7ce)] - **test**: fix compilation problem on debian 8 (NickNaso) [#1138](https://github.com/nodejs/node-addon-api/pull/1138) +* \[[`5790c55784`](https://github.com/nodejs/node-addon-api/commit/5790c55784)] - **src**: do not use non-static class member for constant value (#1134) (Anna Henningsen) +* \[[`b7659db945`](https://github.com/nodejs/node-addon-api/commit/b7659db945)] - Merge pull request #1130 from meixg/main (Jack) +* \[[`a840d51d21`](https://github.com/nodejs/node-addon-api/commit/a840d51d21)] - Add test case for Object Set using uint32 as key (meixg) +* \[[`2c88a7ec4c`](https://github.com/nodejs/node-addon-api/commit/2c88a7ec4c)] - Merge pull request #1132 from JckXia/test-wfl-run (Jack) +* \[[`d3a5ed3869`](https://github.com/nodejs/node-addon-api/commit/d3a5ed3869)] - _**Revert**_ "window CI to running on 2019" (JckXia) +* \[[`cee899ade5`](https://github.com/nodejs/node-addon-api/commit/cee899ade5)] - **src**: allow customization of ObjectWrap behavior (Aaron Meriwether) [#1125](https://github.com/nodejs/node-addon-api/pull/1125) +* \[[`91879b4082`](https://github.com/nodejs/node-addon-api/commit/91879b4082)] - remove window-latest to debug (JckXia) +* \[[`1593ef46ee`](https://github.com/nodejs/node-addon-api/commit/1593ef46ee)] - Testing CI run (JckXia) +* \[[`744c8d2410`](https://github.com/nodejs/node-addon-api/commit/744c8d2410)] - **test**: enhance the test framework (Deepak Rajamohan) + +## 2022-01-21 Version 4.3.0, @NickNaso + +### Notable changes: + +#### API + +- Added iterator for `Napi::Object`. +- Fixed usage of `napi_extended_error_info` in `Napi::Error::New()`. +- Added unwrapping logic to handle graceful error handling for primitives. + +#### TEST + +- Removed travis config. +- Updated compiler used for testing. +- Added BigInt value test. +- Minor fixes all overtest suite. + +### Documentation + +- Documentation of iterator for `Napi::Object`. +- Minor fixes all over documentation. + +### Commits + +* [[`7046834305`](https://github.com/nodejs/node-addon-api/commit/7046834305)] - Update to use recent version of stale action (Michael Dawson) +* [[`293c7327ad`](https://github.com/nodejs/node-addon-api/commit/293c7327ad)] - Merge pull request #1075 from JckXia/handle-error-thrown (Jack) +* [[`706b19986d`](https://github.com/nodejs/node-addon-api/commit/706b19986d)] - **test**: create tools/eslint-format (Doni Rubiagatra) [#1080](https://github.com/nodejs/node-addon-api/pull/1080) +* [[`e0567d098a`](https://github.com/nodejs/node-addon-api/commit/e0567d098a)] - Update documents (JckXia) +* [[`691813842e`](https://github.com/nodejs/node-addon-api/commit/691813842e)] - Refactor code. Using hard coded string instead of using symbol (JckXia) +* [[`7423cc5025`](https://github.com/nodejs/node-addon-api/commit/7423cc5025)] - Update object\_wrap.md (#1094) (Alexander Floh) +* [[`5aab27e6e1`](https://github.com/nodejs/node-addon-api/commit/5aab27e6e1)] - **doc**: add blurb about SetInstanceData (Gabriel Schulhof) +* [[`e439222fe6`](https://github.com/nodejs/node-addon-api/commit/e439222fe6)] - **test**: add bigint value test (WenheLI) [#1096](https://github.com/nodejs/node-addon-api/pull/1096) +* [[`0dfa89f4ef`](https://github.com/nodejs/node-addon-api/commit/0dfa89f4ef)] - **doc**: document object iterators (#1090) (Darshan Sen) +* [[`04b26a9d9b`](https://github.com/nodejs/node-addon-api/commit/04b26a9d9b)] - **test**: add first set of func Ref tests (JckXia) [#1035](https://github.com/nodejs/node-addon-api/pull/1035) +* [[`a0b3fe9197`](https://github.com/nodejs/node-addon-api/commit/a0b3fe9197)] - Replace magic value with symbol (JckXia) +* [[`173c5bc9d9`](https://github.com/nodejs/node-addon-api/commit/173c5bc9d9)] - Update PR based on review comments (JckXia) +* [[`02bcfbccfd`](https://github.com/nodejs/node-addon-api/commit/02bcfbccfd)] - Update doc and appending GUID to object property (JckXia) +* [[`c89f0bfb0b`](https://github.com/nodejs/node-addon-api/commit/c89f0bfb0b)] - Remove un-necessary comment/iostream and updated docs to reflect on limitations with this impl (JckXia) +* [[`ed4d1c51c4`](https://github.com/nodejs/node-addon-api/commit/ed4d1c51c4)] - Added unwrapping logic to handle graceful error handling for primitives (JckXia) +* [[`4663453eae`](https://github.com/nodejs/node-addon-api/commit/4663453eae)] - **src**: fix usage of `napi_extended_error_info` in `Error::New()` (Darshan Sen) [#1092](https://github.com/nodejs/node-addon-api/pull/1092) +* [[`cb228418e6`](https://github.com/nodejs/node-addon-api/commit/cb228418e6)] - **doc**: fix typo in TypedThreadSafeFunction example (#1083) (Tobias Nießen) +* [[`b70acdda1f`](https://github.com/nodejs/node-addon-api/commit/b70acdda1f)] - **test**: remove travis config (#1082) (Michael Dawson) +* [[`1404b7cbea`](https://github.com/nodejs/node-addon-api/commit/1404b7cbea)] - **test**: update compiler used for testing (#1079) (Michael Dawson) +* [[`4351bffd53`](https://github.com/nodejs/node-addon-api/commit/4351bffd53)] - **doc**: fixup to meet lint rules (Michael Dawson) [#1077](https://github.com/nodejs/node-addon-api/pull/1077) +* [[`bd8f6e6d1a`](https://github.com/nodejs/node-addon-api/commit/bd8f6e6d1a)] - **src**: add iterator for Object (Darshan Sen) +* [[`d8fc7b869a`](https://github.com/nodejs/node-addon-api/commit/d8fc7b869a)] - **lint**: add eslint based on config-semistandard (#1067) (Doni Rubiagatra) + +## 2021-09-17 Version 4.2.0, @NickNaso + +### Notable changes: + +#### API + +- Allow creating Function with move-only functor. +- Fixed casts to not be undefined behavior. + +#### TEST + +- Fixed the way to enable C++ exceptions. +- Run tests with options to prefix build root path. + +### Documentation + +- Fixed documentation about how to enable C++ exception. +- Minor fixes all over documentation. + +### Commits + +* [[`2dc1f5b66c`](https://github.com/nodejs/node-addon-api/commit/2dc1f5b66c)] - Merge pull request #1065 from strager/move-only-functor (Nicola Del Gobbo) +* [[`2b57a4aa4c`](https://github.com/nodejs/node-addon-api/commit/2b57a4aa4c)] - **src**: fix casts to not be undefined behavior (Anna Henningsen) [#1070](https://github.com/nodejs/node-addon-api/pull/1070) +* [[`76de4d8222`](https://github.com/nodejs/node-addon-api/commit/76de4d8222)] - **docs**: fix typos (#1068) (todoroff) +* [[`22a2f3c926`](https://github.com/nodejs/node-addon-api/commit/22a2f3c926)] - **docs**: fix typo and formatting (#1062) (strager) +* [[`62b666c34c`](https://github.com/nodejs/node-addon-api/commit/62b666c34c)] - **test**: run tests with opts to prefix bld root path (Deepak Rajamohan) [#1055](https://github.com/nodejs/node-addon-api/pull/1055) +* [[`cbac3aac5d`](https://github.com/nodejs/node-addon-api/commit/cbac3aac5d)] - **test**: standardize unit test file names (Deepak Rajamohan) [#1056](https://github.com/nodejs/node-addon-api/pull/1056) +* [[`3e5897a78b`](https://github.com/nodejs/node-addon-api/commit/3e5897a78b)] - **src,test**: allow creating Function with move-only functor (Matthew "strager" Glazar) +* [[`da2e754a02`](https://github.com/nodejs/node-addon-api/commit/da2e754a02)] - **test**: fix errors reported by newer compiler (Michael Dawson) +* [[`9aaf3b1324`](https://github.com/nodejs/node-addon-api/commit/9aaf3b1324)] - **doc**: fix documentation about how to enable C++ exception (#1059) (Nicola Del Gobbo) [#1059](https://github.com/nodejs/node-addon-api/pull/1059) +* [[`b2f861987f`](https://github.com/nodejs/node-addon-api/commit/b2f861987f)] - **test**: fixed the way to enable C++ exceptions. (#1061) (Nicola Del Gobbo) [#1061](https://github.com/nodejs/node-addon-api/pull/1061) + +## 2021-08-25 Version 4.1.0, @NickNaso + +### Notable changes: + +#### API + +- `Napi::Reference` updated the default value to reflect the most possible +values when there are any errors occurred on `napi_reference_unref`. +- Added the check for nullpointer on `Napi::String` initialization. +- Added the wraps for `napi_add_env_cleanup_hook` and +`napi_remove_env_cleanup_hook`. +- Added `Napi::Maybe` class to handle pending exception when cpp exception +disabled. + +#### TEST + +- Added first set of tests for `Napi::Symbol`. +- Updated test suite to avoid parallel running. + +### Documentation + +- Updated example for context sensitivity. + +### Commits + +* [[`3615041423`](https://github.com/nodejs/node-addon-api/commit/3615041423)] - **src**: return Maybe on pending exception when cpp exception disabled (legendecas) [#927](https://github.com/nodejs/node-addon-api/pull/927) +* [[`10564a43c6`](https://github.com/nodejs/node-addon-api/commit/10564a43c6)] - **src**: add AddCleanupHook (Kevin Eady) [#1014](https://github.com/nodejs/node-addon-api/pull/1014) +* [[`a459f5cc8f`](https://github.com/nodejs/node-addon-api/commit/a459f5cc8f)] - **doc**: update tests to avoid running in parallel (Michael Dawson) [#1024](https://github.com/nodejs/node-addon-api/pull/1024) +* [[`6697c51d1d`](https://github.com/nodejs/node-addon-api/commit/6697c51d1d)] - **src,test**: fix up null char \* exception thrown (Gabriel Schulhof) [#1019](https://github.com/nodejs/node-addon-api/pull/1019) +* [[`e02e8a4ce3`](https://github.com/nodejs/node-addon-api/commit/e02e8a4ce3)] - **test**: add first set of symbol tests (JckXia) [#972](https://github.com/nodejs/node-addon-api/pull/972) +* [[`da50b51398`](https://github.com/nodejs/node-addon-api/commit/da50b51398)] - **test**: dd check for nullptr inside String init (JckXia) [#1015](https://github.com/nodejs/node-addon-api/pull/1015) +* [[`627dbf3c37`](https://github.com/nodejs/node-addon-api/commit/627dbf3c37)] - **doc**: update examples for context sensitivity (Kevin Eady) [#1013](https://github.com/nodejs/node-addon-api/pull/1013) +* [[`37a9b8e753`](https://github.com/nodejs/node-addon-api/commit/37a9b8e753)] - **src**: set default return value of Reference Ref/Unref to 0 (legendecas) [#1004](https://github.com/nodejs/node-addon-api/pull/1004) + +## 2021-06-15 Version 4.0.0, @NickNaso + +### Notable changes: + +#### API + +- Fixed a crashing issue in `Napi::Error::ThrowAsJavaScriptException` +introducing the preprocessor directive `NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS`. +- Fixed compilation problem for GCC 11 and C++20. + +#### TEST + +- Added test for function reference call and contructor. + +### Documentation + +- Updated the oldest Node.js version supported from `10.x` to `12.x`. + +### Commits + +* [[`028107f686`](https://github.com/nodejs/node-addon-api/commit/028107f686)] - **src**: fix Error::ThrowAsJavaScriptException crash (rudolftam) [#975](https://github.com/nodejs/node-addon-api/pull/975) +* [[`fed13534c5`](https://github.com/nodejs/node-addon-api/commit/fed13534c5)] - **src**: fix gcc-11 c++20 compilation (Kevin Eady) [#1009](https://github.com/nodejs/node-addon-api/pull/1009) +* [[`b75afc4d29`](https://github.com/nodejs/node-addon-api/commit/b75afc4d29)] - **test**: function reference call & construct (legendecas) [#1005](https://github.com/nodejs/node-addon-api/pull/1005) + +## 2021-05-28 Version 3.2.1, @NickNaso + +### Notable changes: + +#### Documentation + +- Fixed documentation about the oldest Node.js version supported. + +### Commits + +* [[`6d41ee5a3a`](https://github.com/nodejs/node-addon-api/commit/6d41ee5a3a)] - Fixed readme for new release. (NickNaso) + +## 2021-05-17 Version 3.2.0, @NickNaso + +### Notable changes: + +#### API + +- Remove unnecessary symbol exposure. +- Fixed leak in `Napi::ObjectWrap` instance for getter and setter method. +- Added `Napi::Object::Freeze` and `Napi::object::Seal` methods. +- `Napi::Reference` is now copyable. + +#### Documentation + +- Added docuemtnation for `Napi::Object::PropertyLValue`. +- Changed all N-API references to Node-API. +- Some minor corrections all over the documentation. + +#### TEST + +- Added tests relating to fetch property from Global Object. +- Added addtiona tests for `Napi::Object`. +- Added test for `Napi::Function` contructors. +- Fixed intermittent failure for `Napi::ThreadSafeFunction` test. +- Some minor corrections all over the test suite. + +### TOOL + +- Added Node.js v16.x to CI. +- Added CI configuration for Windows. +- Some fixex on linter command. + +### Commits + +* [[`52721312f6`](https://github.com/nodejs/node-addon-api/commit/52721312f6)] - **docs**: add napi-rs iin Other Bindings section (#999) (LongYinan) +* [[`78a6570a42`](https://github.com/nodejs/node-addon-api/commit/78a6570a42)] - **doc**: fix typo in code example (#997) (Tobias Nießen) +* [[`da3bd5778f`](https://github.com/nodejs/node-addon-api/commit/da3bd5778f)] - **test**: fix undoc assumptions about the timing of tsfn calls (legendecas) [#995](https://github.com/nodejs/node-addon-api/pull/995) +* [[`410cf6a81e`](https://github.com/nodejs/node-addon-api/commit/410cf6a81e)] - **src**: return bool on object freeze and seal (#991) (legendecas) +* [[`93f1898312`](https://github.com/nodejs/node-addon-api/commit/93f1898312)] - **src**: return bool on object set and define property (#977) (legendecas) +* [[`331c2ee274`](https://github.com/nodejs/node-addon-api/commit/331c2ee274)] - **build**: add Node.js v16.x to CI (#983) (legendecas) +* [[`b6f5eb15e6`](https://github.com/nodejs/node-addon-api/commit/b6f5eb15e6)] - **test**: run test suites with helpers (legendecas) [#976](https://github.com/nodejs/node-addon-api/pull/976) +* [[`fbcdf00ea0`](https://github.com/nodejs/node-addon-api/commit/fbcdf00ea0)] - **test**: rename misspelled parameters (Tobias Nießen) [#973](https://github.com/nodejs/node-addon-api/pull/973) +* [[`63a6c32e80`](https://github.com/nodejs/node-addon-api/commit/63a6c32e80)] - **test**: fix intermittent TSFN crashes (Kevin Eady) [#974](https://github.com/nodejs/node-addon-api/pull/974) +* [[`8f120b033f`](https://github.com/nodejs/node-addon-api/commit/8f120b033f)] - **fix**: key for wapping drawing's system condition (#970) (Kévin VOYER) +* [[`1c9d528d66`](https://github.com/nodejs/node-addon-api/commit/1c9d528d66)] - **doc**: correct struct definition (#969) (Darshan Sen) +* [[`5e64d1fa61`](https://github.com/nodejs/node-addon-api/commit/5e64d1fa61)] - Added badges for Node-API v7 and v8. (#954) (Nicola Del Gobbo) +* [[`6ce629b3fa`](https://github.com/nodejs/node-addon-api/commit/6ce629b3fa)] - **src**: add pull request template (#967) (Michael Dawson) +* [[`98126661af`](https://github.com/nodejs/node-addon-api/commit/98126661af)] - Update CONTRIBUTING.md (#966) (Michael Dawson) +* [[`77350eee98`](https://github.com/nodejs/node-addon-api/commit/77350eee98)] - **src**: added Freeze and Seal method to Object class. (NickNaso) [#955](https://github.com/nodejs/node-addon-api/pull/955) +* [[`bc5147cc4a`](https://github.com/nodejs/node-addon-api/commit/bc5147cc4a)] - Finished tests relating to fetch property from Global Object (JckXia) +* [[`0127813111`](https://github.com/nodejs/node-addon-api/commit/0127813111)] - **doc**: unambiguously mark deprecated signatures (Tobias Nießen) [#942](https://github.com/nodejs/node-addon-api/pull/942) +* [[`787e216105`](https://github.com/nodejs/node-addon-api/commit/787e216105)] - **doc**: rename N-API with Node-API (Darshan Sen) [#951](https://github.com/nodejs/node-addon-api/pull/951) +* [[`628023689a`](https://github.com/nodejs/node-addon-api/commit/628023689a)] - **src**: rename N-API with Node-API on comments (NickNaso) [#953](https://github.com/nodejs/node-addon-api/pull/953) +* [[`5c6391578f`](https://github.com/nodejs/node-addon-api/commit/5c6391578f)] - **build**: add CI configuration for Windows (NickNaso) [#948](https://github.com/nodejs/node-addon-api/pull/948) +* [[`8ef07251ec`](https://github.com/nodejs/node-addon-api/commit/8ef07251ec)] - **doc**: added some warnings for buffer and array buffer factory method. (#929) (Nicola Del Gobbo) +* [[`6490b1f730`](https://github.com/nodejs/node-addon-api/commit/6490b1f730)] - **doc**: sync Object::Set value arg with Value::From (#933) (Tobias Nießen) +* [[`7319a0d7a2`](https://github.com/nodejs/node-addon-api/commit/7319a0d7a2)] - Fix tab indent (#938) (Tobias Nießen) +* [[`1916cb937e`](https://github.com/nodejs/node-addon-api/commit/1916cb937e)] - **chore**: fixup linter commands (#940) (legendecas) +* [[`fc4585fa23`](https://github.com/nodejs/node-addon-api/commit/fc4585fa23)] - **test**: dd tests for Function constructors (JoseExposito) [#937](https://github.com/nodejs/node-addon-api/pull/937) +* [[`87b7aae469`](https://github.com/nodejs/node-addon-api/commit/87b7aae469)] - **doc**: warn about SuppressDestruct() (#926) (Anna Henningsen) +* [[`71494a49a3`](https://github.com/nodejs/node-addon-api/commit/71494a49a3)] - **src,doc**: refactor to replace typedefs with usings (Darshan Sen) [#910](https://github.com/nodejs/node-addon-api/pull/910) +* [[`298ff8d9d2`](https://github.com/nodejs/node-addon-api/commit/298ff8d9d2)] - **test**: add additional tests for Object (JoseExposito) [#923](https://github.com/nodejs/node-addon-api/pull/923) +* [[`8a1147b430`](https://github.com/nodejs/node-addon-api/commit/8a1147b430)] - **revert**: src: add additional tests for Function (Michael Dawson) +* [[`bb56ffaa6f`](https://github.com/nodejs/node-addon-api/commit/bb56ffaa6f)] - **doc**: fix documentation for object api (Nicola Del Gobbo) [#931](https://github.com/nodejs/node-addon-api/pull/931) +* [[`3b8bddab49`](https://github.com/nodejs/node-addon-api/commit/3b8bddab49)] - **src**: add additional tests for Function (José Expósito) [#928](https://github.com/nodejs/node-addon-api/pull/928) +* [[`74ab50c775`](https://github.com/nodejs/node-addon-api/commit/74ab50c775)] - **src**: allow references to be copyable in APIs (legendecas) [#915](https://github.com/nodejs/node-addon-api/pull/915) +* [[`929709d0fe`](https://github.com/nodejs/node-addon-api/commit/929709d0fe)] - **doc**: add propertylvalue.md (#925) (Gabriel Schulhof) +* [[`69d0d98be4`](https://github.com/nodejs/node-addon-api/commit/69d0d98be4)] - fixup (Anna Henningsen) +* [[`46e41d961b`](https://github.com/nodejs/node-addon-api/commit/46e41d961b)] - fixup (Anna Henningsen) +* [[`1af1642fb7`](https://github.com/nodejs/node-addon-api/commit/1af1642fb7)] - **doc**: warn about SuppressDestruct() (Anna Henningsen) +* [[`12c548b2ff`](https://github.com/nodejs/node-addon-api/commit/12c548b2ff)] - **tools**: fix error detection (#914) (Darshan Sen) +* [[`458d895d5b`](https://github.com/nodejs/node-addon-api/commit/458d895d5b)] - **packaging**: list files to be published to npm (Lovell Fuller) [#889](https://github.com/nodejs/node-addon-api/pull/889) +* [[`f7ed2490d4`](https://github.com/nodejs/node-addon-api/commit/f7ed2490d4)] - **test**: remove outdated V8 flag (Darshan Sen) [#895](https://github.com/nodejs/node-addon-api/pull/895) +* [[`a575a6ec60`](https://github.com/nodejs/node-addon-api/commit/a575a6ec60)] - **src**: fix leak in ObjectWrap instance set/getters (Kevin Eady) [#899](https://github.com/nodejs/node-addon-api/pull/899) +* [[`b6e844e0b0`](https://github.com/nodejs/node-addon-api/commit/b6e844e0b0)] - **doc**: fix spelling of "targeted" and "targeting" (#904) (Tobias Nießen) +* [[`4d856f6e91`](https://github.com/nodejs/node-addon-api/commit/4d856f6e91)] - **src**: remove unnecessary symbol exposure (Gabriel Schulhof) [#896](https://github.com/nodejs/node-addon-api/pull/896) +* [[`f35bb7d0d7`](https://github.com/nodejs/node-addon-api/commit/f35bb7d0d7)] - **doc**: Update GitHub URL references from 'master' to 'HEAD' (#898) (Jim Schlight) +* [[`286ae215d1`](https://github.com/nodejs/node-addon-api/commit/286ae215d1)] - Add warning about branch rename (Michael Dawson) +* [[`a4a7b28288`](https://github.com/nodejs/node-addon-api/commit/a4a7b28288)] - Update branch references from master to main (#886) (Jim Schlight) +* [[`a2ad0a107a`](https://github.com/nodejs/node-addon-api/commit/a2ad0a107a)] - **docs**: add NAN to N-API resource link (#880) (kidneysolo) +* [[`1c040eeb63`](https://github.com/nodejs/node-addon-api/commit/1c040eeb63)] - **test**: load testModules automatically (raisinten) [#876](https://github.com/nodejs/node-addon-api/pull/876) +* [[`bf478e4496`](https://github.com/nodejs/node-addon-api/commit/bf478e4496)] - **src**: use NAPI\_NOEXCEPT macro instead of noexcept (NickNaso) [#864](https://github.com/nodejs/node-addon-api/pull/864) +* [[`744705f2eb`](https://github.com/nodejs/node-addon-api/commit/744705f2eb)] - **test**: refactor remove repeated execution index.js (raisinten) [#839](https://github.com/nodejs/node-addon-api/pull/839) +* [[`db62e3c811`](https://github.com/nodejs/node-addon-api/commit/db62e3c811)] - Update team members (Michael Dawson) + ## 2020-12-17 Version 3.1.0, @NickNaso ### Notable changes: @@ -7,12 +700,12 @@ #### API - Added `Napi::TypedThreadSafeFunction` class that is a new implementation for -thread-safe functions. +thread-safe functions. - Fixed leak on `Napi::AsyncProgressWorkerBase`. -- Fixed empty data on `Napi::AsyncProgressWorker::OnProgress` caused by race +- Fixed empty data on `Napi::AsyncProgressWorker::OnProgress` caused by race conditions of `Napi::AsyncProgressWorker`. - Added `Napi::ArrayBuffer::Detach()` and `Napi::ArrayBuffer::IsDetached()`. -- Fixed problem on `Napi::FinalizeCallback` it needs to create a +- Fixed problem on `Napi::FinalizeCallback` it needs to create a `Napi::HandleScope` when it calls `Napi::ObjectWrap::~ObjectWrap()`. #### Documentation @@ -83,7 +776,7 @@ conditions of `Napi::AsyncProgressWorker`. #### API - Introduced `include_dir` for use with **gyp** in a scalar context. -- Added `Napi::Addon` to help handle the loading of a native add-on into +- Added `Napi::Addon` to help handle the loading of a native add-on into multiple threads and or multiple times in the same thread. - Concentrate callbacks provided to core N-API. - Make sure wrapcallback is used. @@ -622,5 +1315,3 @@ yet backported in the previous Node.js version. * [0a899bf1c5] - doc: update indication of latest version (Michael Dawson) https://github.com/nodejs/node-addon-api/pull/211 * [17c74e5a5e] - n-api: RangeError in napi_create_dataview() (Jinho Bang) https://github.com/nodejs/node-addon-api/pull/214 * [4058a29989] - n-api: fix memory leak in napi_async_destroy() (Jinho Bang) https://github.com/nodejs/node-addon-api/pull/213 - - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index eb07a975e..97e54b4d3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ # Code of Conduct The Node.js Code of Conduct, which applies to this project, can be found at -https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md. +https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d9fdf926..663fc2304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,164 @@ -# **node-addon-api** Contribution Philosophy +# Contributing to **node-addon-api** + +* [Code of Conduct](#code-of-conduct) +* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) +* [Tests](#tests) +* [Debug](#debug) +* [Benchmarks](#benchmarks) +* [node-addon-api Contribution Philosophy](#node-addon-api-contribution-philosophy) + +## Code of Conduct + +The Node.js project has a +[Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md) +to which all contributors must adhere. + +See [details on our policy on Code of Conduct](https://github.com/nodejs/node/blob/main/doc/contributing/code-of-conduct.md). + + + +## Developer's Certificate of Origin 1.1 + +
+By making a contribution to this project, I certify that:
+
+ (a) The contribution was created in whole or in part by me and I
+     have the right to submit it under the open source license
+     indicated in the file; or
+
+ (b) The contribution is based upon previous work that, to the best
+     of my knowledge, is covered under an appropriate open source
+     license and I have the right under that license to submit that
+     work with modifications, whether created in whole or in part
+     by me, under the same open source license (unless I am
+     permitted to submit under a different license), as indicated
+     in the file; or
+
+ (c) The contribution was provided directly to me by some other
+     person who certified (a), (b) or (c) and I have not modified
+     it.
+
+ (d) I understand and agree that this project and the contribution
+     are public and that a record of the contribution (including all
+     personal information I submit with it, including my sign-off) is
+     maintained indefinitely and may be redistributed consistent with
+     this project or the open source license(s) involved.
+
+ + +## Tests + +To run the **node-addon-api** tests do: + +``` +npm install +npm test +``` + +To avoid testing the deprecated portions of the API run +``` +npm install +npm test --disable-deprecated +``` + +To run the tests targeting a specific version of Node-API run +``` +npm install +export NAPI_VERSION=X +npm test --NAPI_VERSION=X +``` + +where X is the version of Node-API you want to target. + +To run a subset of the test suite, filter conditions are available. +The `--filter` option limits which JavaScript test modules are executed by +`node test`. The default `pretest` step is still `node-gyp rebuild -C test`, +so `npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. + +**Example:** + perform the default test rebuild, then run only the `objectwrap` test module + ``` + npm test --filter=objectwrap + ``` + +Multiple test modules can be selected with wildcards. + +**Example:** +perform the default test rebuild, then run all test modules ending with +`reference`: +`function_reference`, `object_reference`, and `reference` + ``` + npm test --filter=*reference + ``` + +Multiple filter conditions can be joined to broaden the test selection. + +**Example:** + perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module + ``` + npm test --filter='*function objectwrap' + ``` + +As an alternative, `ninja` can be used to build the tests. Please +follow the instructions in [Build with ninja](doc/contributing/build_with_ninja.md). + +## Debug + +To run the **node-addon-api** tests with `--debug` option: + +``` +npm run-script dev +``` + +If you want a faster build, you might use the following option: + +``` +npm run-script dev:incremental +``` + +Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/HEAD/test)** + +## Benchmarks + +You can run the available benchmarks using the following command: + +``` +npm run-script benchmark +``` + +See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. + +## **node-addon-api** Contribution Philosophy The **node-addon-api** team loves contributions. There are many ways in which you can contribute to **node-addon-api**: -- Source code fixes +- [New APIs](#new-apis) +- [Source code fixes](#source-changes) - Additional tests - Documentation improvements -- Joining the N-API working group and participating in meetings +- Joining the Node-API working group and participating in meetings + +### New APIs + +As new APIs are added to Node-API, node-addon-api must be updated to provide +wrappers for those new APIs. For this reason, node-addon-api provides +methods that allow callers to obtain the underlying Node-API handles so +direct calls to Node-API and the use of the objects/methods provided by +node-addon-api can be used together. For example, in order to be able +to use an API for which the node-addon-api does not yet provide a wrapper. -## Source changes +APIs exposed by node-addon-api are generally used to create and +manipulate JavaScript values. Concepts and operations generally map +to ideas specified in the **ECMA262 Language Specification**. -**node-addon-api** is meant to be a thin convenience wrapper around N-API. With this -in mind, contributions of any new APIs that wrap around a core N-API API will -be considered for merge. However, changes that wrap existing **node-addon-api** +### Source changes + +**node-addon-api** is meant to be a thin convenience wrapper around Node-API. With this +in mind, contributions of any new APIs that wrap around a core Node-API API will +be considered for merging. However, changes that wrap existing **node-addon-api** APIs are encouraged to instead be provided as an ecosystem module. The **node-addon-api** team is happy to link to a curated set of modules that build on top of **node-addon-api** if they have broad usefulness to the community and promote @@ -19,28 +166,30 @@ a recommended idiom or pattern. ### Rationale -The N-API team considered a couple different approaches with regards to changes +The Node-API team considered a couple of different approaches with regard to changes extending **node-addon-api** - Larger core module - Incorporate these helpers and patterns into **node-addon-api** - Extras package - Create a new package (strawman name '**node-addon-api**-extras') that contain utility classes and methods that help promote good patterns and idioms while writing native addons with **node-addon-api**. -- Ecosystem - Encourage creation of a module ecosystem around **node-addon-api** +- Ecosystem - Encourage the creation of a module ecosystem around **node-addon-api** where folks can build on top of it. #### Larger Core + This is probably our simplest option in terms of immediate action needed. It would involve landing any open PRs against **node-addon-api**, and continuing to encourage folks to make PRs for utility helpers against the same repository. The downside of the approach is the following: - Less coherency for our API set -- More maintenance burden on the N-API WG core team. +- More maintenance burden on the Node-API WG core team. #### Extras Package -This involves us spinning up a new package which contains the utility classes + +This involves us spinning up a new package that contains the utility classes and methods. This has the benefit of having a separate module where helpers -which make it easier to implement certain patterns and idioms for native addons +make it easier to implement certain patterns and idioms for native addons easier. The downside of this approach is the following: @@ -48,10 +197,11 @@ The downside of this approach is the following: community understand where a particular contribution should be directed to (what belongs in **node-addon-api** vs **node-addon-api-extras**) - Need to define the level of support/API guarantees -- Unclear if the maintenance burden on the N-API WG is reduced or not +- Unclear if the maintenance burden on the Node-API WG is reduced or not #### Ecosystem -This doesn't require a ton of up-front work from the N-API WG. Instead of + +This doesn't require a ton of up-front work from the Node-API WG. Instead of accepting utility PRs into **node-addon-api** or creating and maintaining a new module, the WG will encourage the creation of an ecosystem of modules that build on top of **node-addon-api**, and provide some level of advertising for these @@ -59,8 +209,7 @@ modules (listing them out on the repository/wiki, using them in workshops/tutori etc). The downside of this approach is the following: -- Potential for lack of visibility - evangelism and education is hard, and module -authors might not find right patterns and instead implement things themselves -- There might be greater friction for the N-API WG in evolving APIs since the +- Potential for lack of visibility. Evangelism and education are hard, and module +authors might not find the right patterns and instead implement things themselves +- There might be greater friction for the Node-API WG in evolving APIs since the ecosystem would have taken dependencies on the API shape of **node-addon-api** - diff --git a/LICENSE.md b/LICENSE.md index e2fad6667..819d91a5b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,13 +1,9 @@ The MIT License (MIT) -===================== -Copyright (c) 2017 Node.js API collaborators ------------------------------------ - -*Node.js API collaborators listed at * +Copyright (c) 2017 [Node.js API collaborators](https://github.com/nodejs/node-addon-api#collaborators) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 1aae5056d..29184db10 100644 --- a/README.md +++ b/README.md @@ -1,273 +1,95 @@ # **node-addon-api module** -This module contains **header-only C++ wrapper classes** which simplify -the use of the C based [N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) -provided by Node.js when using C++. It provides a C++ object model -and exception handling semantics with low overhead. - -There are three options for implementing addons: N-API, nan, or direct -use of internal V8, libuv and Node.js libraries. Unless there is a need for -direct access to functionality which is not exposed by N-API as outlined -in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html) -in Node.js core, use N-API. Refer to -[C/C++ addons with N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) -for more information on N-API. - -N-API is an ABI stable C interface provided by Node.js for building native -addons. It is independent from the underlying JavaScript runtime (e.g. V8 or ChakraCore) -and is maintained as part of Node.js itself. It is intended to insulate -native addons from changes in the underlying JavaScript engine and allow -modules compiled for one version to run on later versions of Node.js without -recompilation. -The `node-addon-api` module, which is not part of Node.js, preserves the benefits -of the N-API as it consists only of inline code that depends only on the stable API -provided by N-API. As such, modules built against one version of Node.js -using node-addon-api should run without having to be rebuilt with newer versions -of Node.js. +[![codecov](https://codecov.io/gh/nodejs/node-addon-api/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nodejs/node-addon-api/tree/main) -It is important to remember that *other* Node.js interfaces such as -`libuv` (included in a project via `#include `) are not ABI-stable across -Node.js major versions. Thus, an addon must use N-API and/or `node-addon-api` -exclusively and build against a version of Node.js that includes an -implementation of N-API (meaning an active LTS version of Node.js) in -order to benefit from ABI stability across Node.js major versions. Node.js -provides an [ABI stability guide][] containing a detailed explanation of ABI -stability in general, and the N-API ABI stability guarantee in particular. +[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) -As new APIs are added to N-API, node-addon-api must be updated to provide -wrappers for those new APIs. For this reason node-addon-api provides -methods that allow callers to obtain the underlying N-API handles so -direct calls to N-API and the use of the objects/methods provided by -node-addon-api can be used together. For example, in order to be able -to use an API for which the node-addon-api does not yet provide a wrapper. +This module contains **header-only C++ wrapper classes** which simplify +the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +provided by Node.js when using C++. It provides a C++ object model +and exception handling semantics with low overhead. -APIs exposed by node-addon-api are generally used to create and -manipulate JavaScript values. Concepts and operations generally map -to ideas specified in the **ECMA262 Language Specification**. +- [API References](doc/README.md) +- [Badges](#badges) +- [Contributing](#contributing) +- [License](#license) -The [N-API Resource](https://nodejs.github.io/node-addon-examples/) offers an -excellent orientation and tips for developers just getting started with N-API -and node-addon-api. +## API References -- **[Setup](#setup)** -- **[API Documentation](#api)** -- **[Examples](#examples)** -- **[Tests](#tests)** -- **[More resource and info about native Addons](#resources)** -- **[Badges](#badges)** -- **[Code of Conduct](CODE_OF_CONDUCT.md)** -- **[Contributors](#contributors)** -- **[License](#license)** +API references are available in the [doc](doc/README.md) directory. -## **Current version: 3.1.0** + +## Current version: 8.9.1 + (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) -[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) - - - -node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. -This allows addons built with it to run with Node.js versions which support the targeted N-API version. +node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. +This allows addons built with it to run with Node.js versions which support the targeted Node-API version. **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 10.x. - -## Setup - - [Installation and usage](doc/setup.md) - - [node-gyp](doc/node-gyp.md) - - [cmake-js](doc/cmake-js.md) - - [Conversion tool](doc/conversion-tool.md) - - [Checker tool](doc/checker-tool.md) - - [Generator](doc/generator.md) - - [Prebuild tools](doc/prebuild_tools.md) - - - -### **API Documentation** - -The following is the documentation for node-addon-api. - - - [Full Class Hierarchy](doc/hierarchy.md) - - [Addon Structure](doc/addon.md) - - Data Types: - - [Env](doc/env.md) - - [CallbackInfo](doc/callbackinfo.md) - - [Reference](doc/reference.md) - - [Value](doc/value.md) - - [Name](doc/name.md) - - [Symbol](doc/symbol.md) - - [String](doc/string.md) - - [Number](doc/number.md) - - [Date](doc/date.md) - - [BigInt](doc/bigint.md) - - [Boolean](doc/boolean.md) - - [External](doc/external.md) - - [Object](doc/object.md) - - [Array](doc/array.md) - - [ObjectReference](doc/object_reference.md) - - [PropertyDescriptor](doc/property_descriptor.md) - - [Function](doc/function.md) - - [FunctionReference](doc/function_reference.md) - - [ObjectWrap](doc/object_wrap.md) - - [ClassPropertyDescriptor](doc/class_property_descriptor.md) - - [Buffer](doc/buffer.md) - - [ArrayBuffer](doc/array_buffer.md) - - [TypedArray](doc/typed_array.md) - - [TypedArrayOf](doc/typed_array_of.md) - - [DataView](doc/dataview.md) - - [Error Handling](doc/error_handling.md) - - [Error](doc/error.md) - - [TypeError](doc/type_error.md) - - [RangeError](doc/range_error.md) - - [Object Lifetime Management](doc/object_lifetime_management.md) - - [HandleScope](doc/handle_scope.md) - - [EscapableHandleScope](doc/escapable_handle_scope.md) - - [Memory Management](doc/memory_management.md) - - [Async Operations](doc/async_operations.md) - - [AsyncWorker](doc/async_worker.md) - - [AsyncContext](doc/async_context.md) - - [AsyncWorker Variants](doc/async_worker_variants.md) - - [Thread-safe Functions](doc/threadsafe.md) - - [ThreadSafeFunction](doc/threadsafe_function.md) - - [TypedThreadSafeFunction](doc/typed_threadsafe_function.md) - - [Promises](doc/promises.md) - - [Version management](doc/version_management.md) - - - -### **Examples** - -Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)** - -- **[Hello World](https://github.com/nodejs/node-addon-examples/tree/master/1_hello_world/node-addon-api)** -- **[Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/master/2_function_arguments/node-addon-api)** -- **[Callbacks](https://github.com/nodejs/node-addon-examples/tree/master/3_callbacks/node-addon-api)** -- **[Object factory](https://github.com/nodejs/node-addon-examples/tree/master/4_object_factory/node-addon-api)** -- **[Function factory](https://github.com/nodejs/node-addon-examples/tree/master/5_function_factory/node-addon-api)** -- **[Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/master/6_object_wrap/node-addon-api)** -- **[Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/master/7_factory_wrap/node-addon-api)** -- **[Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/master/8_passing_wrapped/node-addon-api)** - - - -### **Tests** - -To run the **node-addon-api** tests do: - -``` -npm install -npm test -``` - -To avoid testing the deprecated portions of the API run -``` -npm install -npm test --disable-deprecated -``` - -To run the tests targetting a specific version of N-API run -``` -npm install -export NAPI_VERSION=X -npm test --NAPI_VERSION=X -``` - -where X is the version of N-API you want to target. - -### **Debug** +The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. -To run the **node-addon-api** tests with `--debug` option: +## Badges -``` -npm run-script dev -``` - -If you want faster build, you might use the following option: - -``` -npm run-script dev:incremental -``` - -Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/master/test)** - -### **Benchmarks** - -You can run the available benchmarks using the following command: - -``` -npm run-script benchmark -``` - -See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. - - - -### **More resource and info about native Addons** -- **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)** -- **[N-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** -- **[N-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)** - -As node-addon-api's core mission is to expose the plain C N-API as C++ -wrappers, tools that facilitate n-api/node-addon-api providing more -convenient patterns on developing a Node.js add-ons with n-api/node-addon-api -can be published to NPM as standalone packages. It is also recommended to tag -such packages with `node-addon-api` to provide more visibility to the community. - -Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). - - - -### **Badges** - -The use of badges is recommended to indicate the minimum version of N-API +The use of badges is recommended to indicate the minimum version of Node-API required for the module. This helps to determine which Node.js major versions are -supported. Addon maintainers can consult the [N-API support matrix][] to determine -which Node.js versions provide a given N-API version. The following badges are +supported. Addon maintainers can consult the [Node-API support matrix][] to determine +which Node.js versions provide a given Node-API version. The following badges are available: -![N-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v1%20Badge.svg) -![N-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v2%20Badge.svg) -![N-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v3%20Badge.svg) -![N-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v4%20Badge.svg) -![N-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v5%20Badge.svg) -![N-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v6%20Badge.svg) -![N-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20Experimental%20Version%20Badge.svg) +![Node-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v1%20Badge.svg) +![Node-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v2%20Badge.svg) +![Node-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v3%20Badge.svg) +![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) +![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) +![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) +![Node-API v7 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v7%20Badge.svg) +![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg) +![Node-API v9 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v9%20Badge.svg) +![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) -## **Contributing** +## Contributing We love contributions from the community to **node-addon-api**! See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. - - ## Team members ### Active + | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Anna Henningsen | [addaleax](https://github.com/addaleax) | | Chengzhong Wu | [legendecas](https://github.com/legendecas) | -| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | -| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | -| Jim Schlight | [jschlight](https://github.com/jschlight) | +| Jack Xia | [JckXia](https://github.com/JckXia) | +| Kevin Eady | [KevinEady](https://github.com/KevinEady) | | Michael Dawson | [mhdawson](https://github.com/mhdawson) | -| Kevin Eady | [KevinEady](https://github.com/KevinEady) | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | +| Vladimir Morozov | [vmoroz](https://github.com/vmoroz) | + +
+ +Emeritus ### Emeritus + | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | | Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | +| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | +| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | | Jason Ginchereau | [jasongin](https://github.com/jasongin) | +| Jim Schlight | [jschlight](https://github.com/jschlight) | | Sampson Gao | [sampsongao](https://github.com/sampsongao) | | Taylor Woll | [boingoing](https://github.com/boingoing) | - +
+ +## License Licensed under [MIT](./LICENSE.md) -[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ -[N-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix +[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#node-api-version-matrix diff --git a/appveyor.yml b/appveyor.yml index 3f08b6837..77e434d7a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ environment: - # https://github.com/jasongin/nvs/blob/master/doc/CI.md + # https://github.com/jasongin/nvs/blob/HEAD/doc/CI.md NVS_VERSION: 1.4.2 matrix: - NODEJS_VERSION: node/10 diff --git a/benchmark/binding.gyp b/benchmark/binding.gyp index 72f68a13e..879d4a569 100644 --- a/benchmark/binding.gyp +++ b/benchmark/binding.gyp @@ -4,22 +4,22 @@ { 'target_name': 'function_args', 'sources': [ 'function_args.cc' ], - 'includes': [ '../except.gypi' ], + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], }, { 'target_name': 'function_args_noexcept', 'sources': [ 'function_args.cc' ], - 'includes': [ '../noexcept.gypi' ], + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], }, { 'target_name': 'property_descriptor', 'sources': [ 'property_descriptor.cc' ], - 'includes': [ '../except.gypi' ], + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], }, { 'target_name': 'property_descriptor_noexcept', 'sources': [ 'property_descriptor.cc' ], - 'includes': [ '../noexcept.gypi' ], + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], }, ] } diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc index 54bdbe342..f82ebfe16 100644 --- a/benchmark/function_args.cc +++ b/benchmark/function_args.cc @@ -1,8 +1,8 @@ #include "napi.h" static napi_value NoArgFunction_Core(napi_env env, napi_callback_info info) { - (void) env; - (void) info; + (void)env; + (void)info; return nullptr; } @@ -12,7 +12,7 @@ static napi_value OneArgFunction_Core(napi_env env, napi_callback_info info) { if (napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr) != napi_ok) { return nullptr; } - (void) argv; + (void)argv; return nullptr; } @@ -22,8 +22,8 @@ static napi_value TwoArgFunction_Core(napi_env env, napi_callback_info info) { if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { return nullptr; } - (void) argv[0]; - (void) argv[1]; + (void)argv[0]; + (void)argv[1]; return nullptr; } @@ -33,9 +33,9 @@ static napi_value ThreeArgFunction_Core(napi_env env, napi_callback_info info) { if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { return nullptr; } - (void) argv[0]; - (void) argv[1]; - (void) argv[2]; + (void)argv[0]; + (void)argv[1]; + (void)argv[2]; return nullptr; } @@ -45,95 +45,128 @@ static napi_value FourArgFunction_Core(napi_env env, napi_callback_info info) { if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { return nullptr; } - (void) argv[0]; - (void) argv[1]; - (void) argv[2]; - (void) argv[3]; + (void)argv[0]; + (void)argv[1]; + (void)argv[2]; + (void)argv[3]; return nullptr; } static void NoArgFunction(const Napi::CallbackInfo& info) { - (void) info; + (void)info; } static void OneArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv0 = info[0]; + (void)argv0; } static void TwoArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; } static void ThreeArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; - Napi::Value argv2 = info[2]; (void) argv2; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; + Napi::Value argv2 = info[2]; + (void)argv2; } static void FourArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; - Napi::Value argv2 = info[2]; (void) argv2; - Napi::Value argv3 = info[3]; (void) argv3; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; + Napi::Value argv2 = info[2]; + (void)argv2; + Napi::Value argv3 = info[3]; + (void)argv3; } #if NAPI_VERSION > 5 class FunctionArgsBenchmark : public Napi::Addon { public: FunctionArgsBenchmark(Napi::Env env, Napi::Object exports) { - DefineAddon(exports, { - InstanceValue("addon", DefineProperties(Napi::Object::New(env), { - InstanceMethod("noArgFunction", &FunctionArgsBenchmark::NoArgFunction), - InstanceMethod("oneArgFunction", - &FunctionArgsBenchmark::OneArgFunction), - InstanceMethod("twoArgFunction", - &FunctionArgsBenchmark::TwoArgFunction), - InstanceMethod("threeArgFunction", - &FunctionArgsBenchmark::ThreeArgFunction), - InstanceMethod("fourArgFunction", - &FunctionArgsBenchmark::FourArgFunction), - }), napi_enumerable), - InstanceValue("addon_templated", - DefineProperties(Napi::Object::New(env), { - InstanceMethod<&FunctionArgsBenchmark::NoArgFunction>( - "noArgFunction"), - InstanceMethod<&FunctionArgsBenchmark::OneArgFunction>( - "oneArgFunction"), - InstanceMethod<&FunctionArgsBenchmark::TwoArgFunction>( - "twoArgFunction"), - InstanceMethod<&FunctionArgsBenchmark::ThreeArgFunction>( - "threeArgFunction"), - InstanceMethod<&FunctionArgsBenchmark::FourArgFunction>( - "fourArgFunction"), - }), napi_enumerable), - }); + DefineAddon( + exports, + { + InstanceValue( + "addon", + DefineProperties( + Napi::Object::New(env), + { + InstanceMethod("noArgFunction", + &FunctionArgsBenchmark::NoArgFunction), + InstanceMethod("oneArgFunction", + &FunctionArgsBenchmark::OneArgFunction), + InstanceMethod("twoArgFunction", + &FunctionArgsBenchmark::TwoArgFunction), + InstanceMethod( + "threeArgFunction", + &FunctionArgsBenchmark::ThreeArgFunction), + InstanceMethod("fourArgFunction", + &FunctionArgsBenchmark::FourArgFunction), + }), + napi_enumerable), + InstanceValue( + "addon_templated", + DefineProperties( + Napi::Object::New(env), + { + InstanceMethod<&FunctionArgsBenchmark::NoArgFunction>( + "noArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::OneArgFunction>( + "oneArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::TwoArgFunction>( + "twoArgFunction"), + InstanceMethod< + &FunctionArgsBenchmark::ThreeArgFunction>( + "threeArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::FourArgFunction>( + "fourArgFunction"), + }), + napi_enumerable), + }); } + private: - void NoArgFunction(const Napi::CallbackInfo& info) { - (void) info; - } + void NoArgFunction(const Napi::CallbackInfo& info) { (void)info; } void OneArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv0 = info[0]; + (void)argv0; } void TwoArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; } void ThreeArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; - Napi::Value argv2 = info[2]; (void) argv2; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; + Napi::Value argv2 = info[2]; + (void)argv2; } void FourArgFunction(const Napi::CallbackInfo& info) { - Napi::Value argv0 = info[0]; (void) argv0; - Napi::Value argv1 = info[1]; (void) argv1; - Napi::Value argv2 = info[2]; (void) argv2; - Napi::Value argv3 = info[3]; (void) argv3; + Napi::Value argv0 = info[0]; + (void)argv0; + Napi::Value argv1 = info[1]; + (void)argv1; + Napi::Value argv2 = info[2]; + (void)argv2; + Napi::Value argv3 = info[3]; + (void)argv3; } }; #endif // NAPI_VERSION > 5 diff --git a/benchmark/function_args.js b/benchmark/function_args.js index e7fb6636f..fdc9b8c2a 100644 --- a/benchmark/function_args.js +++ b/benchmark/function_args.js @@ -2,7 +2,7 @@ const path = require('path'); const Benchmark = require('benchmark'); const addonName = path.basename(__filename, '.js'); -[ addonName, addonName + '_noexcept' ] +[addonName, addonName + '_noexcept'] .forEach((addonName) => { const rootAddon = require('bindings')({ bindings: addonName, @@ -20,7 +20,7 @@ const addonName = path.basename(__filename, '.js'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].noArgFunction; return suite.add(implem.padStart(maxNameLength, ' '), () => fn()); - }, new Benchmark.Suite) + }, new Benchmark.Suite()) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -28,7 +28,7 @@ const addonName = path.basename(__filename, '.js'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].oneArgFunction; return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x')); - }, new Benchmark.Suite) + }, new Benchmark.Suite()) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -36,7 +36,7 @@ const addonName = path.basename(__filename, '.js'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].twoArgFunction; return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12)); - }, new Benchmark.Suite) + }, new Benchmark.Suite()) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -45,7 +45,7 @@ const addonName = path.basename(__filename, '.js'); const fn = rootAddon[implem].threeArgFunction; return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12, true)); - }, new Benchmark.Suite) + }, new Benchmark.Suite()) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -54,7 +54,7 @@ const addonName = path.basename(__filename, '.js'); const fn = rootAddon[implem].fourArgFunction; return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12, true, anObject)); - }, new Benchmark.Suite) + }, new Benchmark.Suite()) .on('cycle', (event) => console.log(String(event.target))) .run(); }); diff --git a/benchmark/index.js b/benchmark/index.js index e4c7391b1..e03d34420 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -6,7 +6,7 @@ const path = require('path'); let benchmarks = []; -if (!!process.env.npm_config_benchmarks) { +if (process.env.npm_config_benchmarks) { benchmarks = process.env.npm_config_benchmarks .split(';') .map((item) => (item + '.js')); diff --git a/benchmark/property_descriptor.cc b/benchmark/property_descriptor.cc index 19803f595..18cafcdfa 100644 --- a/benchmark/property_descriptor.cc +++ b/benchmark/property_descriptor.cc @@ -1,7 +1,7 @@ #include "napi.h" static napi_value Getter_Core(napi_env env, napi_callback_info info) { - (void) info; + (void)info; napi_value result; napi_status status = napi_create_uint32(env, 42, &result); NAPI_THROW_IF_FAILED(env, status, nullptr); @@ -14,7 +14,7 @@ static napi_value Setter_Core(napi_env env, napi_callback_info info) { napi_status status = napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr); NAPI_THROW_IF_FAILED(env, status, nullptr); - (void) argv; + (void)argv; return nullptr; } @@ -23,22 +23,23 @@ static Napi::Value Getter(const Napi::CallbackInfo& info) { } static void Setter(const Napi::CallbackInfo& info) { - (void) info[0]; + (void)info[0]; } #if NAPI_VERSION > 5 class PropDescBenchmark : public Napi::Addon { public: PropDescBenchmark(Napi::Env, Napi::Object exports) { - DefineAddon(exports, { - InstanceAccessor("addon", - &PropDescBenchmark::Getter, - &PropDescBenchmark::Setter, - napi_enumerable), - InstanceAccessor<&PropDescBenchmark::Getter, - &PropDescBenchmark::Setter>("addon_templated", - napi_enumerable), - }); + DefineAddon(exports, + { + InstanceAccessor("addon", + &PropDescBenchmark::Getter, + &PropDescBenchmark::Setter, + napi_enumerable), + InstanceAccessor<&PropDescBenchmark::Getter, + &PropDescBenchmark::Setter>( + "addon_templated", napi_enumerable), + }); } private: @@ -47,39 +48,31 @@ class PropDescBenchmark : public Napi::Addon { } void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) { - (void) info[0]; - (void) val; + (void)info[0]; + (void)val; } }; #endif // NAPI_VERSION > 5 static Napi::Object Init(Napi::Env env, Napi::Object exports) { napi_status status; - napi_property_descriptor core_prop = { - "core", - nullptr, - nullptr, - Getter_Core, - Setter_Core, - nullptr, - napi_enumerable, - nullptr - }; + napi_property_descriptor core_prop = {"core", + nullptr, + nullptr, + Getter_Core, + Setter_Core, + nullptr, + napi_enumerable, + nullptr}; status = napi_define_properties(env, exports, 1, &core_prop); NAPI_THROW_IF_FAILED(env, status, Napi::Object()); - exports.DefineProperty( - Napi::PropertyDescriptor::Accessor(env, - exports, - "cplusplus", - Getter, - Setter, - napi_enumerable)); + exports.DefineProperty(Napi::PropertyDescriptor::Accessor( + env, exports, "cplusplus", Getter, Setter, napi_enumerable)); - exports.DefineProperty( - Napi::PropertyDescriptor::Accessor("templated", - napi_enumerable)); + exports.DefineProperty(Napi::PropertyDescriptor::Accessor( + "templated", napi_enumerable)); #if NAPI_VERSION > 5 PropDescBenchmark::Init(env, exports); diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js index 848aaaf4a..83ff9b7ea 100644 --- a/benchmark/property_descriptor.js +++ b/benchmark/property_descriptor.js @@ -2,15 +2,15 @@ const path = require('path'); const Benchmark = require('benchmark'); const addonName = path.basename(__filename, '.js'); -[ addonName, addonName + '_noexcept' ] +[addonName, addonName + '_noexcept'] .forEach((addonName) => { const rootAddon = require('bindings')({ bindings: addonName, module_root: __dirname }); delete rootAddon.path; - const getters = new Benchmark.Suite; - const setters = new Benchmark.Suite; + const getters = new Benchmark.Suite(); + const setters = new Benchmark.Suite(); const maxNameLength = Object.keys(rootAddon) .reduce((soFar, value) => Math.max(soFar, value.length), 0); @@ -18,11 +18,12 @@ const addonName = path.basename(__filename, '.js'); Object.keys(rootAddon).forEach((key) => { getters.add(`${key} getter`.padStart(maxNameLength + 7), () => { + // eslint-disable-next-line no-unused-vars const x = rootAddon[key]; }); setters.add(`${key} setter`.padStart(maxNameLength + 7), () => { rootAddon[key] = 5; - }) + }); }); getters diff --git a/common.gypi b/common.gypi index 9be254f0b..5fda7e77a 100644 --- a/common.gypi +++ b/common.gypi @@ -1,10 +1,11 @@ { 'variables': { - 'NAPI_VERSION%': " + +## API Documentation + +The following is the documentation for node-addon-api. + + - [Full Class Hierarchy](hierarchy.md) + - [Addon Structure](addon.md) + - Data Types: + - [BasicEnv](basic_env.md) + - [Env](env.md) + - [CallbackInfo](callbackinfo.md) + - [Reference](reference.md) + - [Value](value.md) + - [Name](name.md) + - [Symbol](symbol.md) + - [String](string.md) + - [Number](number.md) + - [Date](date.md) + - [BigInt](bigint.md) + - [Boolean](boolean.md) + - [External](external.md) + - [Object](object.md) + - [Array](array.md) + - [ObjectReference](object_reference.md) + - [PropertyDescriptor](property_descriptor.md) + - [Function](function.md) + - [FunctionReference](function_reference.md) + - [ObjectWrap](object_wrap.md) + - [ClassPropertyDescriptor](class_property_descriptor.md) + - [Buffer](buffer.md) + - [ArrayBuffer](array_buffer.md) + - [SharedArrayBuffer](shared_array_buffer.md) + - [TypedArray](typed_array.md) + - [TypedArrayOf](typed_array_of.md) + - [DataView](dataview.md) + - [Error Handling](error_handling.md) + - [Error](error.md) + - [TypeError](type_error.md) + - [RangeError](range_error.md) + - [SyntaxError](syntax_error.md) + - [Object Lifetime Management](object_lifetime_management.md) + - [HandleScope](handle_scope.md) + - [EscapableHandleScope](escapable_handle_scope.md) + - [Finalization](finalization.md) + - [Memory Management](memory_management.md) + - [Async Operations](async_operations.md) + - [AsyncWorker](async_worker.md) + - [AsyncContext](async_context.md) + - [AsyncWorker Variants](async_worker_variants.md) + - [Thread-safe Functions](threadsafe.md) + - [ThreadSafeFunction](threadsafe_function.md) + - [TypedThreadSafeFunction](typed_threadsafe_function.md) + - [Promises](promises.md) + - [Version management](version_management.md) + + + +## Examples + +Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)** + +- [Hello World](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/1_hello_world) +- [Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/2_function_arguments/node-addon-api) +- [Callbacks](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/3_callbacks/node-addon-api) +- [Object factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/4_object_factory/node-addon-api) +- [Function factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/5_function_factory/node-addon-api) +- [Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/6_object_wrap/node-addon-api) +- [Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/7_factory_wrap/node-addon-api) +- [Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api) + + + +## ABI Stability Guideline + +It is important to remember that *other* Node.js interfaces such as +`libuv` (included in a project via `#include `) are not ABI-stable across +Node.js major versions. Thus, an addon must use Node-API and/or `node-addon-api` +exclusively and build against a version of Node.js that includes an +implementation of Node-API (meaning an active LTS version of Node.js) in +order to benefit from ABI stability across Node.js major versions. Node.js +provides an [ABI stability guide][] containing a detailed explanation of ABI +stability in general, and the Node-API ABI stability guarantee in particular. + + + +## More resource and info about native Addons + +There are three options for implementing addons: Node-API, nan, or direct +use of internal V8, libuv, and Node.js libraries. Unless there is a need for +direct access to functionality that is not exposed by Node-API as outlined +in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html) +in Node.js core, use Node-API. Refer to +[C/C++ addons with Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +for more information on Node-API. + +- [C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html) +- [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +- [Node-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs) +- [How We Migrated Realm JavaScript From NAN to Node-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api) + +As node-addon-api's core mission is to expose the plain C Node-API as C++ +wrappers, tools that facilitate n-api/node-addon-api providing more +convenient patterns for developing a Node.js add-on with n-api/node-addon-api +can be published to NPM as standalone packages. It is also recommended to tag +such packages with `node-addon-api` to provide more visibility to the community. + +Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). + + + +## Other bindings + +- [napi-rs](https://napi.rs) - (`Rust`) + +[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ diff --git a/doc/addon.md b/doc/addon.md index 96dae718d..7e5ec4791 100644 --- a/doc/addon.md +++ b/doc/addon.md @@ -90,6 +90,12 @@ to either attach methods, accessors, and/or values to the `exports` object or to create its own `exports` object and attach methods, accessors, and/or values to it. +**Note:** `Napi::Addon` uses `Napi::Env::SetInstanceData()` internally. This +means that the add-on should only use `Napi::Env::GetInstanceData` explicitly to +retrieve the instance of the `Napi::Addon` class. Variables whose scope would +otherwise be global should be stored as instance variables in the +`Napi::Addon` class. + Functions created with `Napi::Function::New()`, accessors created with `PropertyDescriptor::Accessor()`, and values can also be attached. If their implementation requires the `ExampleAddon` instance, it can be retrieved from diff --git a/doc/array.md b/doc/array.md index a12e92daf..34badc243 100644 --- a/doc/array.md +++ b/doc/array.md @@ -9,7 +9,7 @@ around `napi_value` representing a JavaScript Array. types such as [`Napi::Int32Array`][] and [`Napi::ArrayBuffer`][], respectively, that can be used for transferring large amounts of data from JavaScript to the native side. An example illustrating the use of a JavaScript-provided -`ArrayBuffer` in native code is available [here](https://github.com/nodejs/node-addon-examples/tree/master/array_buffer_to_native/node-addon-api). +`ArrayBuffer` in native code is available [here](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api). ## Constructor ```cpp diff --git a/doc/array_buffer.md b/doc/array_buffer.md index 346fe6ace..de05e55b3 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -23,12 +23,21 @@ Returns a new `Napi::ArrayBuffer` instance. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::ArrayBuffer` instance. The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it to be valid for the lifetime of the instance. Since the `Napi::ArrayBuffer` is subject to garbage collection this overload is only -suitable for data which is static and never needs to be freed. +suitable for data which is static and never needs to be freed. This factory +method will not provide the caller with an opportunity to free the data when the +`Napi::ArrayBuffer` gets garbage-collected. If you need to free the data +retained by the `Napi::ArrayBuffer` object please use other variants of the +`Napi::ArrayBuffer::New` factory method that accept `Napi::Finalizer`, which is +a function that will be invoked when the `Napi::ArrayBuffer` object has been +destroyed. See [Finalization][] for more details. ```cpp static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength); @@ -42,6 +51,9 @@ Returns a new `Napi::ArrayBuffer` instance. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::ArrayBuffer` instance. The `Napi::ArrayBuffer` instance does not assume ownership for the data and @@ -60,14 +72,17 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: A function to be called when the `Napi::ArrayBuffer` is - destroyed. It must implement `operator()`, accept a `void*` (which is the - `externalData` pointer), and return `void`. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*)`. + See [Finalization][] for more details. Returns a new `Napi::ArrayBuffer` instance. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::ArrayBuffer` instance. The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it @@ -87,11 +102,10 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: The function to be called when the `Napi::ArrayBuffer` is - destroyed. It must implement `operator()`, accept a `void*` (which is the - `externalData` pointer) and `Hint*`, and return `void`. -- `[in] finalizeHint`: The hint to be passed as the second parameter of the - finalize callback. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*, + Hint*)`. See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns a new `Napi::ArrayBuffer` instance. @@ -147,3 +161,5 @@ bool Napi::ArrayBuffer::IsDetached() const; Returns `true` if this `ArrayBuffer` has been detached. [`Napi::Object`]: ./object.md +[External Buffer]: ./external_buffer.md +[Finalization]: ./finalization.md diff --git a/doc/async_context.md b/doc/async_context.md index b217d336a..06a606cd9 100644 --- a/doc/async_context.md +++ b/doc/async_context.md @@ -61,8 +61,8 @@ Returns the `Napi::Env` environment in which the async context has been created. Napi::AsyncContext::operator napi_async_context() const; ``` -Returns the N-API `napi_async_context` wrapped by the `Napi::AsyncContext` -object. This can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_async_context` wrapped by the `Napi::AsyncContext` +object. This can be used to mix usage of the C Node-API and node-addon-api. ## Example diff --git a/doc/async_operations.md b/doc/async_operations.md index 064a9c50f..30efefe45 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -19,7 +19,7 @@ asynchronous operations: - **[`Napi::AsyncWorker`](async_worker.md)** -These class helps manage asynchronous operations through an abstraction +This class helps manage asynchronous operations through an abstraction of the concept of moving data between the **event loop** and **worker threads**. Also, the above class may not be appropriate for every scenario. When using any diff --git a/doc/async_worker.md b/doc/async_worker.md index b6bb0cd67..2250d541d 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -343,8 +343,8 @@ virtual Napi::AsyncWorker::~AsyncWorker(); Napi::AsyncWorker::operator napi_async_work() const; ``` -Returns the N-API napi_async_work wrapped by the `Napi::AsyncWorker` object. This -can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_async_work` wrapped by the `Napi::AsyncWorker` object. This +can be used to mix usage of the C Node-API and node-addon-api. ## Example @@ -418,6 +418,7 @@ Value Echo(const CallbackInfo& info) { EchoWorker* wk = new EchoWorker(cb, in); wk->Queue(); return info.Env().Undefined(); +} ``` Using the implementation of a `Napi::AsyncWorker` is straight forward. You only diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 54007762c..876131aa8 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -51,8 +51,11 @@ virtual void Napi::AsyncProgressWorker::OnOK(); ### OnProgress -This method is invoked when the computation in the `Napi::AsyncProgressWorker::ExecutionProcess::Send` -method was called during worker thread execution. +This method is invoked when the computation in the +`Napi::AsyncProgressWorker::ExecutionProgress::Send` method was called during +worker thread execution. This method can also be triggered via a call to +`Napi::AsyncProgress[Queue]Worker::ExecutionProgress::Signal`, in which case the +`data` parameter will be `nullptr`. ```cpp virtual void Napi::AsyncProgressWorker::OnProgress(const T* data, size_t count) @@ -224,7 +227,7 @@ unexpected upcoming thread safe calls. virtual Napi::AsyncProgressWorker::~AsyncProgressWorker(); ``` -# AsyncProgressWorker::ExecutionProcess +# AsyncProgressWorker::ExecutionProgress A bridge class created before the worker thread execution of `Napi::AsyncProgressWorker::Execute`. @@ -232,15 +235,15 @@ A bridge class created before the worker thread execution of `Napi::AsyncProgres ### Send -`Napi::AsyncProgressWorker::ExecutionProcess::Send` takes two arguments, a pointer +`Napi::AsyncProgressWorker::ExecutionProgress::Send` takes two arguments, a pointer to a generic type of data, and a `size_t` to indicate how many items the pointer is pointing to. The data pointed to will be copied to internal slots of `Napi::AsyncProgressWorker` so -after the call to `Napi::AsyncProgressWorker::ExecutionProcess::Send` the data can +after the call to `Napi::AsyncProgressWorker::ExecutionProgress::Send` the data can be safely released. -Note that `Napi::AsyncProgressWorker::ExecutionProcess::Send` merely guarantees +Note that `Napi::AsyncProgressWorker::ExecutionProgress::Send` merely guarantees **eventual** invocation of `Napi::AsyncProgressWorker::OnProgress`, which means multiple send might be coalesced into single invocation of `Napi::AsyncProgressWorker::OnProgress` with latest data. If you would like to guarantee that there is one invocation of @@ -248,7 +251,16 @@ with latest data. If you would like to guarantee that there is one invocation of class instead which is documented further down this page. ```cpp -void Napi::AsyncProgressWorker::ExecutionProcess::Send(const T* data, size_t count) const; +void Napi::AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const; +``` + +### Signal + +`Napi::AsyncProgressWorker::ExecutionProgress::Signal` triggers an invocation of +`Napi::AsyncProgressWorker::OnProgress` with `nullptr` as the `data` parameter. + +```cpp +void Napi::AsyncProgressWorker::ExecutionProgress::Signal(); ``` ## Example @@ -375,7 +387,7 @@ const exampleCallback = (errorResponse, okResponse, progressData) => { // ... }; -// Call our native addon with the paramters of a string and a function +// Call our native addon with the parameters of a string and a function nativeAddon.echo("example", exampleCallback); ``` @@ -390,7 +402,7 @@ thread in the order it was committed. For the most basic use, only the `Napi::AsyncProgressQueueWorker::Execute` and `Napi::AsyncProgressQueueWorker::OnProgress` method must be implemented in a subclass. -# AsyncProgressQueueWorker::ExecutionProcess +# AsyncProgressQueueWorker::ExecutionProgress A bridge class created before the worker thread execution of `Napi::AsyncProgressQueueWorker::Execute`. @@ -398,28 +410,37 @@ A bridge class created before the worker thread execution of `Napi::AsyncProgres ### Send -`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` takes two arguments, a pointer +`Napi::AsyncProgressQueueWorker::ExecutionProgress::Send` takes two arguments, a pointer to a generic type of data, and a `size_t` to indicate how many items the pointer is pointing to. The data pointed to will be copied to internal slots of `Napi::AsyncProgressQueueWorker` so -after the call to `Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` the data can +after the call to `Napi::AsyncProgressQueueWorker::ExecutionProgress::Send` the data can be safely released. -`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` guarantees invocation +`Napi::AsyncProgressQueueWorker::ExecutionProgress::Send` guarantees invocation of `Napi::AsyncProgressQueueWorker::OnProgress`, which means multiple `Send` call will result in the in-order invocation of `Napi::AsyncProgressQueueWorker::OnProgress` with each data item. ```cpp -void Napi::AsyncProgressQueueWorker::ExecutionProcess::Send(const T* data, size_t count) const; +void Napi::AsyncProgressQueueWorker::ExecutionProgress::Send(const T* data, size_t count) const; +``` + +### Signal + +`Napi::AsyncProgressQueueWorker::ExecutionProgress::Signal` triggers an invocation of +`Napi::AsyncProgressQueueWorker::OnProgress` with `nullptr` as the `data` parameter. + +```cpp +void Napi::AsyncProgressQueueWorker::ExecutionProgress::Signal() const; ``` ## Example -The code below show an example of the `Napi::AsyncProgressQueueWorker` implementation, but -also demonsrates how to use multiple `Napi::Function`'s if you wish to provide multiple -callback functions for more object oriented code: +The code below shows an example of the `Napi::AsyncProgressQueueWorker` implementation, but +also demonstrates how to use multiple `Napi::Function`'s if you wish to provide multiple +callback functions for more object-oriented code: ```cpp #include @@ -550,7 +571,7 @@ const onProgressCallback = (num) => { // ... }; -// Call our native addon with the paramters of a string and three callback functions +// Call our native addon with the parameters of a string and three callback functions nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); ``` diff --git a/doc/basic_env.md b/doc/basic_env.md new file mode 100644 index 000000000..7a5b430f1 --- /dev/null +++ b/doc/basic_env.md @@ -0,0 +1,200 @@ +# BasicEnv + +The data structure containing the environment in which the request is being run. + +The `Napi::BasicEnv` object is usually created and passed by the Node.js runtime +or node-addon-api infrastructure. + +The `Napi::BasicEnv` object represents an environment that has a limited subset +of APIs when compared to `Napi::Env` and can be used in basic finalizers. See +[Finalization][] for more details. + +## Methods + +### Constructor + +```cpp +Napi::BasicEnv::BasicEnv(node_api_nogc_env env); +``` + +- `[in] env`: The `node_api_nogc_env` environment from which to construct the + `Napi::BasicEnv` object. + +### node_api_nogc_env + +```cpp +operator node_api_nogc_env() const; +``` + +Returns the `node_api_nogc_env` opaque data structure representing the +environment. + +### GetInstanceData +```cpp +template T* GetInstanceData() const; +``` + +Returns the instance data that was previously associated with the environment, +or `nullptr` if none was associated. + +### SetInstanceData + + +```cpp +template using Finalizer = void (*)(Env, T*); +template fini = Env::DefaultFini> +void SetInstanceData(T* data) const; +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If +not given, the default finalizer will be used, which simply uses the `delete` +operator to destroy `T*` when the add-on instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle. + +Associates a data item stored at `T* data` with the current instance of the +add-on. The item will be passed to the function `fini` which gets called when an +instance of the add-on is unloaded. + +### SetInstanceData + +```cpp +template +using FinalizerWithHint = void (*)(Env, DataType*, HintType*); +template fini = + Env::DefaultFiniWithHint> +void SetInstanceData(DataType* data, HintType* hint) const; +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form `void CleanupData(Napi::Env env, DataType* data, +HintType* hint)`. If not given, the default finalizer will be used, which simply +uses the `delete` operator to destroy `T*` when the add-on instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle. +- `[in] hint`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle and will be passed as a hint to +`fini` when the add-on instance is unloaded. + +Associates a data item stored at `T* data` with the current instance of the +add-on. The item will be passed to the function `fini` which gets called when an +instance of the add-on is unloaded. This overload accepts an additional hint to +be passed to `fini`. + +### GetModuleFileName + +```cpp +const char* Napi::Env::GetModuleFileName() const; +``` + +Returns a URL containing the absolute path of the location from which the add-on +was loaded. For a file on the local file system it will start with `file://`. +The string is null-terminated and owned by env and must thus not be modified or +freed. It is only valid while the add-on is loaded. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook); +``` + +- `[in] hook`: A function to call when the environment exits. Accepts a function + of the form `void ()`. + +Registers `hook` as a function to be run once the current Node.js environment +exits. Unlike the underlying C-based Node-API, providing the same `hook` +multiple times **is** allowed. The hooks will be called in reverse order, i.e. +the most recently added one will be called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env)`. See [Finalization][] for + more details. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization][] + for more details. +- `[in] data`: The data to associate with the object. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See + [Finalization][] for more details. +- `[in] data`: The data to associate with the object. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook, Arg* arg); +``` + +- `[in] hook`: A function to call when the environment exits. Accepts a function + of the form `void (Arg* arg)`. +- `[in] arg`: A pointer to data that will be passed as the argument to `hook`. + +Registers `hook` as a function to be run with the `arg` parameter once the +current Node.js environment exits. Unlike the underlying C-based Node-API, +providing the same `hook` and `arg` pair multiple times **is** allowed. The +hooks will be called in reverse order, i.e. the most recently added one will be +called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +# Env::CleanupHook + +The `Env::CleanupHook` object allows removal of the hook added via +`Env::AddCleanupHook()` + +## Methods + +### IsEmpty + +```cpp +bool IsEmpty(); +``` + +Returns `true` if the cleanup hook was **not** successfully registered. + +### Remove + +```cpp +bool Remove(Env env); +``` + +Unregisters the hook from running once the current Node.js environment exits. + +Returns `true` if the hook was successfully removed from the Node.js +environment. + +[Finalization]: ./finalization.md diff --git a/doc/buffer.md b/doc/buffer.md index 97ed48a5a..548400481 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -22,12 +22,20 @@ Returns a new `Napi::Buffer` object. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::Buffer` object. -The `Napi::Buffer` object does not assume ownership for the data and expects it to be -valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage -collection this overload is only suitable for data which is static and never -needs to be freed. +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. Since the `Napi::Buffer` is subject +to garbage collection this overload is only suitable for data which is static +and never needs to be freed. This factory method will not provide the caller +with an opportunity to free the data when the `Napi::Buffer` gets +garbage-collected. If you need to free the data retained by the `Napi::Buffer` +object please use other variants of the `Napi::Buffer::New` factory method that +accept `Finalizer`, which is a function that will be invoked when the +`Napi::Buffer` object has been destroyed. See [Finalization][] for more details. ```cpp static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length); @@ -41,6 +49,9 @@ Returns a new `Napi::Buffer` object. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::Buffer` object. The `Napi::Buffer` object does not assume ownership for the data and expects it @@ -58,14 +69,17 @@ static Napi::Buffer Napi::Buffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept a `T*` (which is the - external data pointer), and return `void`. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. Returns a new `Napi::Buffer` object. ### New +> When `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` is defined, this method is not available. +> See [External Buffer][] for more information. + Wraps the provided external data into a new `Napi::Buffer` object. The `Napi::Buffer` object does not assume ownership for the data and expects it to be @@ -84,11 +98,96 @@ static Napi::Buffer Napi::Buffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept a `T*` (which is the - external data pointer) and `Hint*`, and return `void`. -- `[in] finalizeHint`: The hint to be passed as the second parameter of the - finalize callback. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +Returns a new `Napi::Buffer` object. + +### NewOrCopy + +Wraps the provided external data into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it. + +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage +collection this overload is only suitable for data which is static and never +needs to be freed. + +This factory method will not provide the caller with an opportunity to free the +data when the `Napi::Buffer` gets garbage-collected. If you need to free the +data retained by the `Napi::Buffer` object please use other variants of the +`Napi::Buffer::New` factory method that accept `Napi::Finalizer`, which is a +function that will be invoked when the `Napi::Buffer` object has been +destroyed. + +```cpp +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, T* data, size_t length); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. + +Returns a new `Napi::Buffer` object. + +### NewOrCopy + +Wraps the provided external data into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it and the `finalizeCallback` is invoked +immediately. + +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. + +Returns a new `Napi::Buffer` object. + +### NewOrCopy + +Wraps the provided external data into a new `Napi::Buffer` object. When the +[external buffer][] is not supported, allocates a new `Napi::Buffer` object and +copies the provided external data into it and the `finalizeCallback` is invoked +immediately. + +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. + +```cpp +template +static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `Napi::Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns a new `Napi::Buffer` object. @@ -142,3 +241,5 @@ size_t Napi::Buffer::Length() const; Returns the number of `T` elements in the external data. [`Napi::Uint8Array`]: ./typed_array_of.md +[External Buffer]: ./external_buffer.md +[Finalization]: ./finalization.md diff --git a/doc/callback_scope.md b/doc/callback_scope.md index 35f0f8d9b..39e4b58fe 100644 --- a/doc/callback_scope.md +++ b/doc/callback_scope.md @@ -2,7 +2,7 @@ There are cases (for example, resolving promises) where it is necessary to have the equivalent of the scope associated with a callback in place when making -certain N-API calls. +certain Node-API calls. ## Methods @@ -50,5 +50,5 @@ Returns the `Napi::Env` associated with the `Napi::CallbackScope`. Napi::CallbackScope::operator napi_callback_scope() const; ``` -Returns the N-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` -object. This can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` +object. This can be used to mix usage of the C Node-API and node-addon-api. diff --git a/doc/checker-tool.md b/doc/checker-tool.md index 135f13fd7..9d755bd3a 100644 --- a/doc/checker-tool.md +++ b/doc/checker-tool.md @@ -2,7 +2,7 @@ **node-addon-api** provides a [checker tool][] that will inspect a given directory tree, identifying all Node.js native addons therein, and further -indicating for each addon whether it is an N-API addon. +indicating for each addon whether it is an Node-API addon. ## To use the checker tool: diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index 92336e7ea..69fe92fa4 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -1,10 +1,18 @@ # Class property and descriptor -Property descriptor for use with `Napi::ObjectWrap::DefineClass()`. -This is different from the standalone `Napi::PropertyDescriptor` because it is -specific to each `Napi::ObjectWrap` subclass. -This prevents using descriptors from a different class when defining a new class -(preventing the callbacks from having incorrect `this` pointers). +Property descriptor for use with `Napi::ObjectWrap` and +`Napi::InstanceWrap`. This is different from the standalone +`Napi::PropertyDescriptor` because it is specific to each +`Napi::ObjectWrap` and `Napi::InstanceWrap` subclasses. +This prevents using descriptors from a different class when defining a new +class (preventing the callbacks from having incorrect `this` pointers). + +`Napi::ClassPropertyDescriptor` is a helper class created with +`Napi::ObjectWrap` and `Napi::InstanceWrap`. For more reference about it +see: + +- [InstanceWrap](./instance_wrap.md) +- [ObjectWrap](./object_wrap.md) ## Example @@ -17,7 +25,6 @@ class Example : public Napi::ObjectWrap { Example(const Napi::CallbackInfo &info); private: - static Napi::FunctionReference constructor; double _value; Napi::Value GetValue(const Napi::CallbackInfo &info); void SetValue(const Napi::CallbackInfo &info, const Napi::Value &value); @@ -31,8 +38,9 @@ Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { InstanceAccessor<&Example::GetValue>("readOnlyProp") }); - constructor = Napi::Persistent(func); - constructor.SuppressDestruct(); + Napi::FunctionReference *constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); exports.Set("Example", func); return exports; @@ -45,8 +53,6 @@ Example::Example(const Napi::CallbackInfo &info) : Napi::ObjectWrap(inf this->_value = value.DoubleValue(); } -Napi::FunctionReference Example::constructor; - Napi::Value Example::GetValue(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); return Napi::Number::New(env, this->_value); @@ -108,10 +114,10 @@ inside the `Napi::ObjectWrap` class. operator napi_property_descriptor&() { return _desc; } ``` -Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` +Returns the original Node-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` ```cpp operator const napi_property_descriptor&() const { return _desc; } ``` -Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` +Returns the original Node-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` diff --git a/doc/cmake-js.md b/doc/cmake-js.md index 08cd3ea8c..1d5df91ca 100644 --- a/doc/cmake-js.md +++ b/doc/cmake-js.md @@ -27,37 +27,56 @@ Your project will require a `CMakeLists.txt` file. The [CMake.js README file](ht ### NAPI_VERSION -When building N-API addons, it's crucial to specify the N-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: +When building Node-API addons, it's crucial to specify the Node-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: ``` add_definitions(-DNAPI_VERSION=3) ``` -Since N-API is ABI-stable, your N-API addon will work, without recompilation, with the N-API version you specify in `NAPI_VERSION` and all subsequent N-API versions. +Since Node-API is ABI-stable, your Node-API addon will work, without recompilation, with the Node-API version you specify in `NAPI_VERSION` and all subsequent Node-API versions. -In the absence of a need for features available only in a specific N-API version, version 3 is a good choice as it is the version of N-API that was active when N-API left experimental status. +In the absence of a need for features available only in a specific Node-API version, version 3 is a good choice as it is the version of Node-API that was active when Node-API left experimental status. ### NAPI_EXPERIMENTAL -The following line in the `CMakeLists.txt` file will enable N-API experimental features if your code requires them: +The following line in the `CMakeLists.txt` file will enable Node-API experimental features if your code requires them: ``` add_definitions(-DNAPI_EXPERIMENTAL) ``` +### Exception Handling + +To enable C++ exception handling (for more info see: [Setup](setup.md)), define +the corresponding preprocessor directives depending on which exception handling +behavior is desired. + +To enable C++ exception handling with `Napi::Error` objects only: + +``` +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS) +``` + +To enable C++ exception handling for all exceptions thrown: + +``` +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS) +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS_ALL) +``` + ### node-addon-api -If your N-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api). +If your Node-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#node-api-and-node-addon-api). ## Example -A working example of an N-API native addon built using CMake.js can be found on the [node-addon-examples repository](https://github.com/nodejs/node-addon-examples/tree/master/build_with_cmake#building-n-api-addons-using-cmakejs). +A working example of an Node-API native addon built using CMake.js can be found on the [node-addon-examples repository](https://github.com/nodejs/node-addon-examples/tree/main/src/8-tooling/build_with_cmake#building-node-api-addons-using-cmakejs). ## **CMake** Reference - [Installation](https://github.com/cmake-js/cmake-js#installation) - [How to use](https://github.com/cmake-js/cmake-js#usage) - - [Using N-API and node-addon-api](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api) + - [Using Node-API and node-addon-api](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api) - [Tutorials](https://github.com/cmake-js/cmake-js#tutorials) - [Use case in the works - ArrayFire.js](https://github.com/cmake-js/cmake-js#use-case-in-the-works---arrayfirejs) diff --git a/doc/contributing/build_with_ninja.md b/doc/contributing/build_with_ninja.md new file mode 100644 index 000000000..d59e9bbef --- /dev/null +++ b/doc/contributing/build_with_ninja.md @@ -0,0 +1,16 @@ +# Build Test with Ninja + +Ninja can be used to speed up building tests with optimized parallelism. + +To build the tests with ninja and node-gyp, run the following commands: + +```sh +/node-addon-api $ node-gyp configure -C test -- -f ninja +/node-addon-api $ ninja -C test/build/Release +# Run tests +/node-addon-api $ node ./test/index.js + +# Run tests with debug addon +/node-addon-api $ ninja -C test/build/Debug +/node-addon-api $ NODE_API_BUILD_CONFIG=Debug node ./test/index.js +``` diff --git a/doc/creating_a_release.md b/doc/contributing/creating_a_release.md similarity index 52% rename from doc/creating_a_release.md rename to doc/contributing/creating_a_release.md index 5c8f8b025..02e9cbc53 100644 --- a/doc/creating_a_release.md +++ b/doc/contributing/creating_a_release.md @@ -6,7 +6,17 @@ collaborators to add you. If necessary you can ask the build Working Group who manages the Node.js npm user to add you if there are no other active collaborators. -## Prerequisites +Generally, the release is handled by the +[release-please](https://github.com/nodejs/node-addon-api/blob/main/.github/workflows/release-please.yml) +GitHub action. It will bump the version in `package.json` and publish +node-addon-api to npm. + +In cases that the release-please action is not working, please follow the steps +below to publish node-addon-api manually. + +## Publish new release manually + +### Prerequisites Before to start creating a new release check if you have installed the following tools: @@ -16,32 +26,34 @@ tools: If not please follow the instruction reported in the tool's documentation to install it. -## Publish new release +### Steps These are the steps to follow to create a new release: * Open an issue in the **node-addon-api** repo documenting the intent to create a new release. Give people some time to comment or suggest PRs that should land first. -* Validate all tests pass by running npm test on master. +* Validate all tests pass by running `npm test` on the `main` branch. * Update the version in **package.json** appropriately. -* Update the [README.md](https://github.com/nodejs/node-addon-api/blob/master/README.md) +* Update the [README.md](https://github.com/nodejs/node-addon-api/blob/main/README.md) to show the new version as the latest. * Generate the changelog for the new version using **changelog maker** tool. From the route folder of the repo launch the following command: ```bash - > changelog-maker + > changelog-maker --md --group --filter-release ``` -* Use the output generated by **changelog maker** to update the [CHANGELOG.md](https://github.com/nodejs/node-addon-api/blob/master/CHANGELOG.md) +* Use the output generated by **changelog maker** to update the [CHANGELOG.md](https://github.com/nodejs/node-addon-api/blob/main/CHANGELOG.md) following the style used in publishing the previous release. * Add any new contributors to the "contributors" section in the package.json -* Validate all tests pass by running npm test on master. +* Commit with a message containing _only_ an x.y.z semver designator. "x.y.z" (so that the commit can be filtered by changelog-maker) + +* Create a release proposal pull request. * Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api-new/)** to validate tests pass (note there are still some issues on SmartOS and @@ -60,3 +72,24 @@ and that the correct version is installed. and close the issue. * Tweet that the release has been created. + +## Optional Steps + +Depending on circumstances for the release, additional steps may be required to +support the release process. + +### Major Releases to Drop Support Node.js Versions + +`node-addon-api` provides support for Node.js versions following the same +[release schedule](https://nodejs.dev/en/about/releases/): once a Node.js +version leaves maintenance mode, the next major version of `node-addon-api` +published will drop support for that version. These are the steps to follow to +drop support for a Node.js version: + +* Update minimum version supported in documentation ([README.md](../README.md)) + +* Remove from GitHub actions ([ci.yml](../.github/workflows/ci.yml) and + [ci-win.yml](../.github/workflows/ci-win.yml)) + +* Remove from Jenkins CI ([node-test-node-addon-api-LTS versions + [Jenkins]](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api-LTS%20versions/)) diff --git a/doc/dataview.md b/doc/dataview.md index 66fb28919..619ceecca 100644 --- a/doc/dataview.md +++ b/doc/dataview.md @@ -6,6 +6,11 @@ The `Napi::DataView` class corresponds to the [JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) class. +**NOTE**: The support for `Napi::DataView::New()` overloads accepting an +`Napi::SharedArrayBuffer` parameter is only available when using +`NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ## Methods ### New @@ -50,6 +55,48 @@ static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayB Returns a new `Napi::DataView` instance. +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. +- `[in] byteLength` : Number of elements in the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + ### Constructor Initializes an empty instance of the `Napi::DataView` class. @@ -75,7 +122,22 @@ Napi::DataView(napi_env env, napi_value value); Napi::ArrayBuffer Napi::DataView::ArrayBuffer() const; ``` -Returns the backing array buffer. +Returns the backing array buffer as an `Napi::ArrayBuffer`. + +**NOTE**: If the `Napi::DataView` is not backed by an `Napi::ArrayBuffer`, this +method will terminate the process with a fatal error when using +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior +otherwise. Use `Buffer()` instead to get the backing buffer without assuming its +type. + +### Buffer + +```cpp +Napi::Value Napi::DataView::Buffer() const; +``` + +Returns the backing array buffer as a generic `Napi::Value`, allowing optional +type-checking with `Is*()` and type-casting with `As<>()` methods. ### ByteOffset diff --git a/doc/date.md b/doc/date.md index 4c5fefa5e..7131b016f 100644 --- a/doc/date.md +++ b/doc/date.md @@ -37,6 +37,20 @@ static Napi::Date Napi::Date::New(Napi::Env env, double value); Returns a new instance of `Napi::Date` object. +### New + +Creates a new instance of a `Napi::Date` object. + +```cpp +static Napi::Date Napi::Date::New(napi_env env, std::chrono::system_clock::time_point time_point); +``` + + - `[in] env`: The environment in which to construct the `Napi::Date` object. + - `[in] value`: The point in time, represented by an + `std::chrono::system_clock::time_point`. + +Returns a new instance of `Napi::Date` object. + ### ValueOf ```cpp diff --git a/doc/env.md b/doc/env.md index 66575ea2c..7773275ee 100644 --- a/doc/env.md +++ b/doc/env.md @@ -1,8 +1,15 @@ # Env -The opaque data structure containing the environment in which the request is being run. +Class `Napi::Env` inherits from class [`Napi::BasicEnv`][]. -The Env object is usually created and passed by the Node.js runtime or node-addon-api infrastructure. +The data structure containing the environment in which the request is being run. + +The `Napi::Env` object is usually created and passed by the Node.js runtime or +node-addon-api infrastructure. + +The `Napi::Env` object represents an environment that has a superset of APIs +when compared to `Napi::BasicEnv` and therefore _cannot_ be used in basic +finalizers. See [Finalization][] for more details. ## Methods @@ -57,7 +64,7 @@ Returns a `bool` indicating if an exception is pending in the environment. ### GetAndClearPendingException ```cpp -Napi::Error Napi::Env::GetAndClearPendingException(); +Napi::Error Napi::Env::GetAndClearPendingException() const; ``` Returns an `Napi::Error` object representing the environment's pending exception, if any. @@ -65,7 +72,7 @@ Returns an `Napi::Error` object representing the environment's pending exception ### RunScript ```cpp -Napi::Value Napi::Env::RunScript(____ script); +Napi::Value Napi::Env::RunScript(____ script) const; ``` - `[in] script`: A string containing JavaScript code to execute. @@ -76,57 +83,5 @@ The `script` can be any of the following types: - `const char *` - `const std::string &` -### GetInstanceData -```cpp -template T* GetInstanceData(); -``` - -Returns the instance data that was previously associated with the environment, -or `nullptr` if none was associated. - -### SetInstanceData - -```cpp -template using Finalizer = void (*)(Env, T*); -template fini = Env::DefaultFini> -void SetInstanceData(T* data); -``` - -- `[template] fini`: A function to call when the instance data is to be deleted. -Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If -not given, the default finalizer will be used, which simply uses the `delete` -operator to destroy `T*` when the addon instance is unloaded. -- `[in] data`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle. - -Associates a data item stored at `T* data` with the current instance of the -addon. The item will be passed to the function `fini` which gets called when an -instance of the addon is unloaded. - -### SetInstanceData - -```cpp -template -using FinalizerWithHint = void (*)(Env, DataType*, HintType*); -template fini = - Env::DefaultFiniWithHint> -void SetInstanceData(DataType* data, HintType* hint); -``` - -- `[template] fini`: A function to call when the instance data is to be deleted. -Accepts a function of the form -`void CleanupData(Napi::Env env, DataType* data, HintType* hint)`. If not given, -the default finalizer will be used, which simply uses the `delete` operator to -destroy `T*` when the addon instance is unloaded. -- `[in] data`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle. -- `[in] hint`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle and will be passed as a hint to -`fini` when the addon instance is unloaded. - -Associates a data item stored at `T* data` with the current instance of the -addon. The item will be passed to the function `fini` which gets called when an -instance of the addon is unloaded. This overload accepts an additional hint to -be passed to `fini`. +[`Napi::BasicEnv`]: ./basic_env.md +[Finalization]: ./finalization.md diff --git a/doc/error_handling.md b/doc/error_handling.md index 9a0ef349e..b4b4ca238 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -14,15 +14,19 @@ If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the `Napi::Error` class extends `std::exception` and enables integrated error-handling for C++ exceptions and JavaScript exceptions. +Note, that due to limitations of the Node-API, if one attempts to cast the error object wrapping a primitive inside a C++ addon, the wrapped object +will be received instead. (With property `4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal` containing the primitive value thrown) + The following sections explain the approach for each case: - [Handling Errors With C++ Exceptions](#exceptions) +- [Handling Errors With Maybe Type and C++ Exceptions Disabled](#noexceptions-maybe) - [Handling Errors Without C++ Exceptions](#noexceptions) -In most cases when an error occurs, the addon should do whatever clean is possible -and then return to JavaScript so that they error can be propagated. In less frequent +In most cases when an error occurs, the addon should do whatever cleanup is possible +and then return to JavaScript so that the error can be propagated. In less frequent cases the addon may be able to recover from the error, clear the error and then continue. @@ -38,14 +42,26 @@ the error as a C++ exception of type `Napi::Error`. If a JavaScript function called by C++ code via node-addon-api throws a JavaScript exception, then node-addon-api automatically converts and throws it as a C++ -exception of type `Napi:Error` on return from the JavaScript code to the native +exception of type `Napi::Error` on return from the JavaScript code to the native method. -If a C++ exception of type `Napi::Error` escapes from a N-API C++ callback, then -the N-API wrapper automatically converts and throws it as a JavaScript exception. +If a C++ exception of type `Napi::Error` escapes from a Node-API C++ callback, then +the Node-API wrapper automatically converts and throws it as a JavaScript exception. + +If other types of C++ exceptions are thrown, node-addon-api will either abort +the process or wrap the exception in an `Napi::Error` in order to throw it as a +JavaScript exception. This behavior is determined by which node-gyp dependency +used: + +- When using the `node_addon_api_except` dependency, only `Napi::Error` objects + will be handled. +- When using the `node_addon_api_except_all` dependency, all exceptions will be +handled. For exceptions derived from `std::exception`, an `Napi::Error` will be +created with the message of the exception's `what()` member function. For all +other exceptions, an `Napi::Error` will be created with a generic error message. -On return from a native method, node-addon-api will automatically convert a pending C++ -exception to a JavaScript exception. +On return from a native method, node-addon-api will automatically convert a pending +`Napi::Error` C++ exception to a JavaScript exception. When C++ exceptions are enabled try/catch can be used to catch exceptions thrown from calls to JavaScript and then they can either be handled or rethrown before @@ -67,10 +83,10 @@ will bubble up as a C++ exception of type `Napi::Error`, until it is either caug while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. -### Propagating a N-API C++ exception +### Propagating a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // other C++ statements // ... @@ -81,10 +97,10 @@ executed. The exception will bubble up as a C++ exception of type `Napi::Error`, until it is either caught while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. -### Handling a N-API C++ exception +### Handling a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result; try { result = jsFunctionThatThrows({ arg1, arg2 }); @@ -96,6 +112,70 @@ try { Since the exception was caught here, it will not be propagated as a JavaScript exception. + + +## Handling Errors With Maybe Type and C++ Exceptions Disabled + +If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the +`Napi::Error` class does not extend `std::exception`. This means that any calls to +node-addon-api functions do not throw a C++ exceptions. Instead, these node-api +functions that call into JavaScript are returning with `Maybe` boxed values. +In that case, the calling side should convert the `Maybe` boxed values with +checks to ensure that the call did succeed and therefore no exception is pending. +If the check fails, that is to say, the returning value is _empty_, the calling +side should determine what to do with `env.GetAndClearPendingException()` before +attempting to call another node-api (for more info see: [Env](env.md)). + +The conversion from the `Maybe` boxed value to the actual return value is +enforced by compilers so that the exceptions must be properly handled before +continuing. + +## Examples with Maybe Type and C++ exceptions disabled + +### Throwing a JS exception + +```cpp +Napi::Env env = ... +Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); +return; +``` + +After throwing a JavaScript exception, the code should generally return +immediately from the native callback, after performing any necessary cleanup. + +### Propagating a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Value result; +if (!maybeResult.To(&result)) { + // The Maybe is empty, calling into js failed, cleaning up... + // It is recommended to return an empty Maybe if the procedure failed. + return result; +} +``` + +If `maybeResult.To(&result)` returns false a JavaScript exception is pending. +To let the exception propagate, the code should generally return immediately +from the native callback, after performing any necessary cleanup. + +### Handling a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +if (maybeResult.IsNothing()) { + Napi::Error e = env.GetAndClearPendingException(); + cerr << "Caught JavaScript exception: " + e.Message(); +} +``` + +Since the exception was cleared here, it will not be propagated as a JavaScript +exception after the native callback returns. + ## Handling Errors Without C++ Exceptions @@ -123,11 +203,11 @@ return; After throwing a JavaScript exception, the code should generally return immediately from the native callback, after performing any necessary cleanup. -### Propagating a N-API JS exception +### Propagating a Node-API JS exception ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Error e = env.GetAndClearPendingException(); @@ -139,11 +219,11 @@ If env.IsExceptionPending() returns true a JavaScript exception is pending. To let the exception propagate, the code should generally return immediately from the native callback, after performing any necessary cleanup. -### Handling a N-API JS exception +### Handling a Node-API JS exception ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Napi::Error e = env.GetAndClearPendingException(); @@ -154,10 +234,10 @@ if (env.IsExceptionPending()) { Since the exception was cleared here, it will not be propagated as a JavaScript exception after the native callback returns. -## Calling N-API directly from a **node-addon-api** addon +## Calling Node-API directly from a **node-addon-api** addon **node-addon-api** provides macros for throwing errors in response to non-OK -`napi_status` results when calling [N-API](https://nodejs.org/docs/latest/api/n-api.html) +`napi_status` results when calling [Node-API](https://nodejs.org/docs/latest/api/n-api.html) functions from within a native addon. These macros are defined differently depending on whether C++ exceptions are enabled or not, but are available for use in either case. diff --git a/doc/escapable_handle_scope.md b/doc/escapable_handle_scope.md index 4f3e2d062..faf4b5b47 100644 --- a/doc/escapable_handle_scope.md +++ b/doc/escapable_handle_scope.md @@ -20,7 +20,7 @@ For more details refer to the section titled Creates a new escapable handle scope. ```cpp -Napi::EscapableHandleScope Napi::EscapableHandleScope::New(Napi:Env env); +Napi::EscapableHandleScope Napi::EscapableHandleScope::New(Napi::Env env); ``` - `[in] Env`: The environment in which to construct the `Napi::EscapableHandleScope` object. @@ -35,22 +35,20 @@ Creates a new escapable handle scope. Napi::EscapableHandleScope Napi::EscapableHandleScope::New(napi_env env, napi_handle_scope scope); ``` -- `[in] env`: napi_env in which the scope passed in was created. -- `[in] scope`: pre-existing napi_handle_scope. +- `[in] env`: `napi_env` in which the scope passed in was created. +- `[in] scope`: pre-existing `napi_handle_scope`. Returns a new `Napi::EscapableHandleScope` instance which wraps the -napi_escapable_handle_scope handle passed in. This can be used -to mix usage of the C N-API and node-addon-api. - -operator EscapableHandleScope::napi_escapable_handle_scope +`napi_escapable_handle_scope` handle passed in. This can be used +to mix usage of the C Node-API and node-addon-api. ```cpp operator Napi::EscapableHandleScope::napi_escapable_handle_scope() const ``` -Returns the N-API napi_escapable_handle_scope wrapped by the `Napi::EscapableHandleScope` object. -This can be used to mix usage of the C N-API and node-addon-api by allowing -the class to be used be converted to a napi_escapable_handle_scope. +Returns the Node-API `napi_escapable_handle_scope` wrapped by the `Napi::EscapableHandleScope` object. +This can be used to mix usage of the C Node-API and node-addon-api by allowing +the class to be used be converted to a `napi_escapable_handle_scope`. ### Destructor ```cpp @@ -67,7 +65,7 @@ guarantee as to when the garbage collector will do this. napi::Value Napi::EscapableHandleScope::Escape(napi_value escapee); ``` -- `[in] escapee`: Napi::Value or napi_env to promote to the outer scope +- `[in] escapee`: `Napi::Value` or `napi_env` to promote to the outer scope Returns `Napi::Value` which can be used in the outer scope. This method can be called at most once on a given `Napi::EscapableHandleScope`. If it is called diff --git a/doc/external.md b/doc/external.md index 814eb037c..4b4603e8e 100644 --- a/doc/external.md +++ b/doc/external.md @@ -1,10 +1,21 @@ # External (template) -Class `Napi::External` inherits from class [`Napi::Value`][]. +Class `Napi::External` inherits from class [`Napi::TypeTaggable`][]. The `Napi::External` template class implements the ability to create a `Napi::Value` object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data. -`Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function. +`Napi::External` objects can be created with an optional Finalizer function and +optional Hint value. The `Finalizer` function, if specified, is called when your +`Napi::External` object is released by Node's garbage collector. It gives your +code the opportunity to free any dynamically created data. If you specify a Hint +value, it is passed to your `Finalizer` function. See [Finalization][] for more details. + +Note that `Napi::Value::IsExternal()` will return `true` for any external value. +It does not differentiate between the templated parameter `T` in +`Napi::External`. It is up to the addon to ensure an `Napi::External` +object holds the correct `T` when retrieving the data via +`Napi::External::Data()`. One method to ensure an object is of a specific +type is through [type tags](./object.md#TypeTag). ## Methods @@ -31,7 +42,9 @@ static Napi::External Napi::External::New(napi_env env, - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. -- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting a T* and returning void. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. Returns the created `Napi::External` object. @@ -47,8 +60,10 @@ static Napi::External Napi::External::New(napi_env env, - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. -- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting T* and Hint* parameters and returning void. -- `[in] finalizeHint`: A hint value passed to the `finalizeCallback` function. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns the created `Napi::External` object. @@ -60,4 +75,5 @@ T* Napi::External::Data() const; Returns a pointer to the arbitrary C++ data held by the `Napi::External` object. -[`Napi::Value`]: ./value.md +[Finalization]: ./finalization.md +[`Napi::TypeTaggable`]: ./type_taggable.md diff --git a/doc/external_buffer.md b/doc/external_buffer.md new file mode 100644 index 000000000..25942436a --- /dev/null +++ b/doc/external_buffer.md @@ -0,0 +1,18 @@ +# External Buffer + +**Some runtimes other than Node.js have dropped support for external buffers**. +On runtimes other than Node.js, node-api methods may return +`napi_no_external_buffers_allowed` to indicate that external +buffers are not supported. One such runtime is Electron as +described in this issue +[electron/issues/35801](https://github.com/electron/electron/issues/35801). + +In order to maintain broadest compatibility with all runtimes, +you may define `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` in your addon before +includes for the node-api and node-addon-api headers. Doing so will hide the +functions that create external buffers. This will ensure a compilation error +occurs if you accidentally use one of these methods. + +In node-addon-api, the `Napi::Buffer::NewOrCopy` provides a convenient way to +create an external buffer, or allocate a new buffer and copy the data when the +external buffer is not supported. diff --git a/doc/finalization.md b/doc/finalization.md new file mode 100644 index 000000000..3dc4e7860 --- /dev/null +++ b/doc/finalization.md @@ -0,0 +1,153 @@ +# Finalization + +Various node-addon-api methods accept a templated `Finalizer finalizeCallback` +parameter. This parameter represents a native callback function that runs in +response to a garbage collection event. A finalizer is considered a _basic_ +finalizer if the callback only utilizes a certain subset of APIs, which may +provide more efficient memory management, optimizations, improved execution, or +other benefits. + +In general, it is best to use basic finalizers whenever possible (eg. when +access to JavaScript is _not_ needed). The +`NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS` preprocessor directive can be defined +to ensure that all finalizers are basic. + +## Finalizers + +The callback takes `Napi::Env` as its first argument: + +### Example + +```cpp +Napi::External::New(Env(), new int(1), [](Napi::Env env, int* data) { + env.RunScript("console.log('Finalizer called')"); + delete data; +}); +``` + +## Basic Finalizers + +Use of basic finalizers may allow the engine to perform optimizations when +scheduling or executing the callback. For example, V8 does not allow access to +the engine heap during garbage collection. Restricting finalizers from accessing +the engine heap allows the callback to execute during garbage collection, +providing a chance to free native memory eagerly. + +In general, APIs that access engine heap are not allowed in basic finalizers. + +The callback takes `Napi::BasicEnv` as its first argument: + +### Example + +```cpp +Napi::ArrayBuffer::New( + Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) { + delete[] static_cast(finalizeData); + }); +``` + +## Scheduling Finalizers + +In addition to passing finalizers to `Napi::External`s and other Node-API +constructs, `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` can be +used to schedule a callback to run outside of the garbage collector +finalization. Since the associated native memory may already be freed by the +basic finalizer, any additional data may be passed eg. via the finalizer's +parameters (`T data*`, `Hint hint*`) or via lambda capture. This allows for +freeing native data in a basic finalizer, while executing any JavaScript code in +an additional finalizer. + +### Example + +```cpp +// Native Add-on + +#include +#include +#include "napi.h" + +using namespace Napi; + +// A structure representing some data that uses a "large" amount of memory. +class LargeData { + public: + LargeData() : id(instances++) {} + size_t id; + + static size_t instances; +}; + +size_t LargeData::instances = 0; + +// Basic finalizer to free `LargeData`. Takes ownership of the pointer and +// frees its memory after use. +void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) { + std::unique_ptr instance(data); + std::cout << "Basic finalizer for instance " << instance->id + << " called\n"; + + // Register a finalizer. Since the instance will be deleted by + // the time this callback executes, pass the instance's `id` via lambda copy + // capture and _not_ a reference capture that accesses `this`. + env.PostFinalizer([instanceId = instance->id](Napi::Env env) { + env.RunScript("console.log('Finalizer for instance " + + std::to_string(instanceId) + " called');"); + }); + + // Free the `LargeData` held in `data` once `instance` goes out of scope. +} + +Value CreateExternal(const CallbackInfo& info) { + // Create a new instance of LargeData. + auto instance = std::make_unique(); + + // Wrap the instance in an External object, registering a basic + // finalizer that will delete the instance to free the "large" amount of + // memory. + return External::New(info.Env(), instance.release(), MyBasicFinalizer); +} + +Object Init(Napi::Env env, Object exports) { + exports["createExternal"] = Function::New(env, CreateExternal); + return exports; +} + +NODE_API_MODULE(addon, Init) +``` + +```js +// JavaScript + +const { createExternal } = require('./addon.node'); + +for (let i = 0; i < 5; i++) { + const ext = createExternal(); + // ... do something with `ext` .. +} + +console.log('Loop complete'); +await new Promise(resolve => setImmediate(resolve)); +console.log('Next event loop cycle'); +``` + +Possible output: + +``` +Basic finalizer for instance 0 called +Basic finalizer for instance 1 called +Basic finalizer for instance 2 called +Basic finalizer for instance 3 called +Basic finalizer for instance 4 called +Loop complete +Finalizer for instance 3 called +Finalizer for instance 4 called +Finalizer for instance 1 called +Finalizer for instance 2 called +Finalizer for instance 0 called +Next event loop cycle +``` + +If the garbage collector runs during the loop, the basic finalizers execute and +display their logging message synchronously during the loop execution. The +additional finalizers execute at some later point after the garbage collection +cycle. diff --git a/doc/function.md b/doc/function.md index ddc089cf7..2b1cbfda7 100644 --- a/doc/function.md +++ b/doc/function.md @@ -60,7 +60,7 @@ This is the type describing a callback returning `void` that will be invoked from JavaScript. ```cpp -typedef void (*VoidCallback)(const Napi::CallbackInfo& info); +using VoidCallback = void (*)(const Napi::CallbackInfo& info); ``` ### Napi::Function::Callback @@ -70,7 +70,7 @@ from JavaScript. ```cpp -typedef Value (*Callback)(const Napi::CallbackInfo& info); +using Callback = Value (*)(const Napi::CallbackInfo& info); ``` ## Methods diff --git a/doc/function_reference.md b/doc/function_reference.md index fa21830b8..07afc643b 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -60,7 +60,7 @@ Napi::FunctionReference::FunctionReference(napi_env env, napi_ref ref); ``` - `[in] env`: The environment in which to construct the `Napi::FunctionReference` object. -- `[in] ref`: The N-API reference to be held by the `Napi::FunctionReference`. +- `[in] ref`: The Node-API reference to be held by the `Napi::FunctionReference`. Returns a newly created `Napi::FunctionReference` object. diff --git a/doc/generator.md b/doc/generator.md index 9167480cf..2ab1f5c2d 100644 --- a/doc/generator.md +++ b/doc/generator.md @@ -3,8 +3,8 @@ ## What is generator **[generator-napi-module](https://www.npmjs.com/package/generator-napi-module)** is a module to quickly generate a skeleton module using -**N-API**, the new API for Native addons. This module automatically sets up your -**gyp file** to use **node-addon-api**, the C++ wrappers for N-API and generates +**Node-API**, the new API for Native addons. This module automatically sets up your +**gyp file** to use **node-addon-api**, the C++ wrappers for Node-API and generates a wrapper JS module. Optionally, it can even configure the generated project to use **TypeScript** instead. diff --git a/doc/handle_scope.md b/doc/handle_scope.md index 1bebb8176..212344604 100644 --- a/doc/handle_scope.md +++ b/doc/handle_scope.md @@ -33,19 +33,17 @@ Napi::HandleScope::HandleScope(Napi::Env env, Napi::HandleScope scope); - `[in] env`: `Napi::Env` in which the scope passed in was created. - `[in] scope`: pre-existing `Napi::HandleScope`. -Returns a new `Napi::HandleScope` instance which wraps the napi_handle_scope -handle passed in. This can be used to mix usage of the C N-API +Returns a new `Napi::HandleScope` instance which wraps the `napi_handle_scope` +handle passed in. This can be used to mix usage of the C Node-API and node-addon-api. -operator HandleScope::napi_handle_scope - ```cpp operator Napi::HandleScope::napi_handle_scope() const ``` -Returns the N-API napi_handle_scope wrapped by the `Napi::EscapableHandleScope` object. -This can be used to mix usage of the C N-API and node-addon-api by allowing -the class to be used be converted to a napi_handle_scope. +Returns the Node-API `napi_handle_scope` wrapped by the `Napi::EscapableHandleScope` object. +This can be used to mix usage of the C Node-API and node-addon-api by allowing +the class to be used be converted to a `napi_handle_scope`. ### Destructor ```cpp @@ -63,3 +61,17 @@ Napi::Env Napi::HandleScope::Env() const; ``` Returns the `Napi::Env` associated with the `Napi::HandleScope`. + +## Example + +```cpp +for (int i = 0; i < LOOP_MAX; i++) { + Napi::HandleScope scope(info.Env()); + std::string name = std::string("inner-scope") + std::to_string(i); + Napi::Value newValue = Napi::String::New(info.Env(), name.c_str()); + // do something with newValue +}; +``` + +For more details refer to the section titled [Object lifetime +management](object_lifetime_management.md). diff --git a/doc/hierarchy.md b/doc/hierarchy.md index 921f94a99..440f7a6c6 100644 --- a/doc/hierarchy.md +++ b/doc/hierarchy.md @@ -20,7 +20,7 @@ | [`Napi::Env`][] | | | [`Napi::Error`][] | [`Napi::ObjectReference`][], [`std::exception`][] | | [`Napi::EscapableHandleScope`][] | | -| [`Napi::External`][] | [`Napi::Value`][] | +| [`Napi::External`][] | [`Napi::TypeTaggable`][] | | [`Napi::Function`][] | [`Napi::Object`][] | | [`Napi::FunctionReference`][] | [`Napi::Reference`][] | | [`Napi::HandleScope`][] | | @@ -28,7 +28,7 @@ | [`Napi::MemoryManagement`][] | | | [`Napi::Name`][] | [`Napi::Value`][] | | [`Napi::Number`][] | [`Napi::Value`][] | -| [`Napi::Object`][] | [`Napi::Value`][] | +| [`Napi::Object`][] | [`Napi::TypeTaggable`][] | | [`Napi::ObjectReference`][] | [`Napi::Reference`][] | | [`Napi::ObjectWrap`][] | [`Napi::InstanceWrap`][], [`Napi::Reference`][] | | [`Napi::Promise`][] | [`Napi::Object`][] | @@ -37,7 +37,9 @@ | [`Napi::Reference`] | | | [`Napi::String`][] | [`Napi::Name`][] | | [`Napi::Symbol`][] | [`Napi::Name`][] | +| [`Napi::SyntaxError`][] | [`Napi::Error`][] | | [`Napi::ThreadSafeFunction`][] | | +| [`Napi::TypeTaggable`][] | [`Napi::Value][] | | [`Napi::TypeError`][] | [`Napi::Error`][] | | [`Napi::TypedArray`][] | [`Napi::Object`][] | | [`Napi::TypedArrayOf`][] | [`Napi::TypedArray`][] | @@ -81,8 +83,10 @@ [`Napi::Reference`]: ./reference.md [`Napi::String`]: ./string.md [`Napi::Symbol`]: ./symbol.md -[`Napi::ThreadSafeFunction`]: ./thread_safe_function.md +[`Napi::SyntaxError`]: ./syntax_error.md +[`Napi::ThreadSafeFunction`]: ./threadsafe_function.md [`Napi::TypeError`]: ./type_error.md +[`Napi::TypeTaggable`]: ./type_taggable.md [`Napi::TypedArray`]: ./typed_array.md [`Napi::TypedArrayOf`]: ./typed_array_of.md [`Napi::Uint8Array`]: ./typed_array_of.md diff --git a/doc/maybe.md b/doc/maybe.md new file mode 100644 index 000000000..dc71c0750 --- /dev/null +++ b/doc/maybe.md @@ -0,0 +1,76 @@ +# Maybe (template) + +Class `Napi::Maybe` represents a value that may be empty: every `Maybe` is +either `Just` and contains a value, or `Nothing`, and does not. `Maybe` types +are very common in node-addon-api code, as they represent that the function may +throw a JavaScript exception and cause the program to be unable to evaluate any +JavaScript code until the exception has been handled. + +Typically, the value wrapped in `Napi::Maybe` is [`Napi::Value`] and its +subclasses. + +## Methods + +### IsNothing + +```cpp +template +bool Napi::Maybe::IsNothing() const; +``` + +Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and +`false` otherwise. + +### IsJust + +```cpp +template +bool Napi::Maybe::IsJust() const; +``` + +Returns `true` if the `Maybe` is `Just` and contains a value, and `false` +otherwise. + +### Check + +```cpp +template +void Napi::Maybe::Check() const; +``` + +Short-hand for `Maybe::Unwrap()`, which doesn't return a value. Could be used +where the actual value of the Maybe is not needed like `Object::Set`. +If this Maybe is nothing (empty), node-addon-api will crash the +process. + +### Unwrap + +```cpp +template +T Napi::Maybe::Unwrap() const; +``` + +Return the value of type `T` contained in the Maybe. If this Maybe is +nothing (empty), node-addon-api will crash the process. + +### UnwrapOr + +```cpp +template +T Napi::Maybe::UnwrapOr(const T& default_value) const; +``` + +Return the value of type T contained in the Maybe, or use a default +value if this Maybe is nothing (empty). + +### UnwrapTo + +```cpp +template +bool Napi::Maybe::UnwrapTo(T* result) const; +``` + +Converts this Maybe to a value of type `T` in the `out`. If this Maybe is +nothing (empty), `false` is returned and `out` is left untouched. + +[`Napi::Value`]: ./value.md diff --git a/doc/memory_management.md b/doc/memory_management.md index afa622550..882c0f802 100644 --- a/doc/memory_management.md +++ b/doc/memory_management.md @@ -17,7 +17,7 @@ more often than it would otherwise in an attempt to garbage collect the JavaScri objects that keep the externally allocated memory alive. ```cpp -static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::Env env, int64_t change_in_bytes); +static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::BasicEnv env, int64_t change_in_bytes); ``` - `[in] env`: The environment in which the API is invoked under. diff --git a/doc/node-gyp.md b/doc/node-gyp.md index 529aa0ea2..a39d5b8c0 100644 --- a/doc/node-gyp.md +++ b/doc/node-gyp.md @@ -4,19 +4,19 @@ C++ code needs to be compiled into executable form whether it be as an object file to linked with others, a shared library, or a standalone executable. The main reason for this is that we need to link to the Node.js dependencies and -headers correctly, another reason is that we need a cross platform way to build +headers correctly. Another reason is that we need a cross-platform way to build C++ source into binary for the target platform. -Until now **node-gyp** is the **de-facto** standard build tool for writing -Node.js addons. It's based on Google's **gyp** build tool, which abstract away -many of the tedious issues related to cross platform building. +**node-gyp** remains the **de-facto** standard build tool for writing +Node.js addons. It's based on Google's **gyp** build tool, which abstracts away +many of the tedious issues related to cross-platform building. -**node-gyp** uses a file called ```binding.gyp``` that is located on the root of +**node-gyp** uses a file called `binding.gyp` that is located in the root of your addon project. -```binding.gyp``` file, contains all building configurations organized with a -JSON like syntax. The most important parameter is the **target** that must be -set to the same value used on the initialization code of the addon as in the +The `binding.gyp` file contains all building configurations organized with a +JSON-like syntax. The most important parameter is the **target** that must be +set to the same value used in the initialization code of the addon, as in the examples reported below: ### **binding.gyp** @@ -41,8 +41,8 @@ examples reported below: // ... /** -* This code is our entry-point. We receive two arguments here, the first is the -* environment that represent an independent instance of the JavaScript runtime, +* This code is our entry point. We receive two arguments here: the first is the +* environment that represent an independent instance of the JavaScript runtime; * the second is exports, the same as module.exports in a .js file. * You can either add properties to the exports object passed in or create your * own exports object. In either case you must return the object to be used as @@ -56,7 +56,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { } /** -* This code defines the entry-point for the Node addon, it tells Node where to go +* This code defines the entry point for the Node addon. It tells Node where to go * once the library has been loaded into active memory. The first argument must * match the "target" in our *binding.gyp*. Using NODE_GYP_MODULE_NAME ensures * that the argument will be correct, as long as the module is built with @@ -75,8 +75,8 @@ NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) - [Command options](https://www.npmjs.com/package/node-gyp#command-options) - [Configuration](https://www.npmjs.com/package/node-gyp#configuration) -Sometimes finding the right settings for ```binding.gyp``` is not easy so to -accomplish at most complicated task please refer to: +Sometimes finding the right settings for `binding.gyp` is not easy, so to +accomplish the most complicated tasks, please refer to: - [GYP documentation](https://gyp.gsrc.io/index.md) -- [node-gyp wiki](https://github.com/nodejs/node-gyp/wiki) +- [node-gyp wiki](https://github.com/nodejs/node-gyp/tree/main/docs) diff --git a/doc/object.md b/doc/object.md index 8fb00a533..fb7d53ad1 100644 --- a/doc/object.md +++ b/doc/object.md @@ -1,6 +1,6 @@ # Object -Class `Napi::Object` inherits from class [`Napi::Value`][]. +Class `Napi::Object` inherits from class [`Napi::TypeTaggable`][]. The `Napi::Object` class corresponds to a JavaScript object. It is extended by the following node-addon-api classes that you may use when working with more specific types: @@ -56,16 +56,7 @@ Napi::Object::Object(napi_env env, napi_value value); ``` - `[in] env`: The `napi_env` environment in which to construct the Value object. -- `[in] value`: The C++ primitive from which to instantiate the Value. `value` may be any of: - - bool - - Any integer type - - Any floating point type - - const char* (encoded using UTF-8, null-terminated) - - const char16_t* (encoded using UTF-16-LE, null-terminated) - - std::string (encoded using UTF-8) - - std::u16string - - Napi::Value - - napi_value +- `[in] value`: The `napi_value` which is a handle for a JavaScript object. Creates a non-empty `Napi::Object` instance. @@ -81,7 +72,7 @@ Creates a new `Napi::Object` value. ### Set() ```cpp -void Napi::Object::Set (____ key, ____ value); +bool Napi::Object::Set (____ key, ____ value) const; ``` - `[in] key`: The name for the property being assigned. - `[in] value`: The value being assigned to the property. @@ -95,18 +86,12 @@ The key can be any of the following types: - `const std::string&` - `uint32_t` -While the value must be any of the following types: -- `napi_value` -- [`Napi::Value`](value.md) -- `const char*` -- `std::string&` -- `bool` -- `double` +The `value` can be of any type that is accepted by [`Napi::Value::From`][]. ### Delete() ```cpp -bool Napi::Object::Delete(____ key); +bool Napi::Object::Delete(____ key) const; ``` - `[in] key`: The name of the property to delete. @@ -158,7 +143,7 @@ Note: This is equivalent to the JavaScript instanceof operator. ### AddFinalizer() ```cpp template -inline void AddFinalizer(Finalizer finalizeCallback, T* data); +inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; ``` - `[in] finalizeCallback`: The function to call when the object is garbage-collected. @@ -176,7 +161,7 @@ where `data` is the pointer that was passed into the call to `AddFinalizer()`. template inline void AddFinalizer(Finalizer finalizeCallback, T* data, - Hint* finalizeHint); + Hint* finalizeHint) const; ``` - `[in] data`: The data to associate with the object. @@ -199,7 +184,7 @@ The properties whose key is a `Symbol` will not be included. ### HasOwnProperty() ```cpp -bool Napi::Object::HasOwnProperty(____ key); const +bool Napi::Object::HasOwnProperty(____ key) const; ``` - `[in] key` The name of the property to check. @@ -215,7 +200,7 @@ The key can be any of the following types: ### DefineProperty() ```cpp -void Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property); +bool Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property) const; ``` - `[in] property`: A [`Napi::PropertyDescriptor`](property_descriptor.md). @@ -224,56 +209,225 @@ Define a property on the object. ### DefineProperties() ```cpp -void Napi::Object::DefineProperties (____ properties) +bool Napi::Object::DefineProperties (____ properties) const; ``` - `[in] properties`: A list of [`Napi::PropertyDescriptor`](property_descriptor.md). Can be one of the following types: - - const std::initializer_list& - - const std::vector& + - const std::initializer_list& + - const std::vector& Defines properties on the object. -### Operator[]() +### Freeze() ```cpp -Napi::PropertyLValue Napi::Object::operator[] (const char* utf8name); +void Napi::Object::Freeze() const; ``` -- `[in] utf8name`: UTF-8 encoded null-terminated property name. -Returns a [`Napi::PropertyLValue`](propertylvalue.md) as the named property or sets the named property. +The `Napi::Object::Freeze()` method freezes an object. A frozen object can no +longer changed. Freezing an object prevents new properties from being added to +it, existing properties from being removed, prevents changing the +enumerability, configurability, or writability of existing properties and +prevents the value of existing properties from being changed. In addition, +freezing an object also prevents its prototype from being changed. + +### Seal() ```cpp -Napi::PropertyLValue Napi::Object::operator[] (const std::string& utf8name); +void Napi::Object::Seal() const; ``` -- `[in] utf8name`: UTF-8 encoded property name. -Returns a [`Napi::PropertyLValue`](propertylvalue.md) as the named property or sets the named property. +The `Napi::Object::Seal()` method seals an object, preventing new properties +from being added to it and marking all existing properties as non-configurable. +Values of present properties can still be changed as long as they are +writable. + +### GetPrototype() ```cpp -Napi::PropertyLValue Napi::Object::operator[] (uint32_t index); +Napi::Object Napi::Object::GetPrototype() const; ``` -- `[in] index`: Element index. -Returns a [`Napi::PropertyLValue`](propertylvalue.md) or sets an indexed property or array element. +The `Napi::Object::GetPrototype()` method returns the prototype of the object. + +### SetPrototype() ```cpp -Napi::Value Napi::Object::operator[] (const char* utf8name) const; +bool Napi::Object::SetPrototype(const Napi::Object& value) const; +``` + +- `[in] value`: The prototype value. + +The `Napi::Object::SetPrototype()` method sets the prototype of the object. + +**NOTE**: The support for `Napi::Object::SetPrototype` is only available when +using `NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + +### operator\[\]() + +```cpp +Napi::PropertyLValue Napi::Object::operator[] (const char* utf8name) const; ``` - `[in] utf8name`: UTF-8 encoded null-terminated property name. -Returns the named property as a [`Napi::Value`](value.md). +Returns a [`Napi::Object::PropertyLValue`](propertylvalue.md) as the named +property or sets the named property. ```cpp -Napi::Value Napi::Object::operator[] (const std::string& utf8name) const; +Napi::PropertyLValue Napi::Object::operator[] (const std::string& utf8name) const; ``` - `[in] utf8name`: UTF-8 encoded property name. -Returns the named property as a [`Napi::Value`](value.md). +Returns a [`Napi::Object::PropertyLValue`](propertylvalue.md) as the named +property or sets the named property. ```cpp -Napi::Value Napi::Object::operator[] (uint32_t index) const; +Napi::PropertyLValue Napi::Object::operator[] (uint32_t index) const; ``` - `[in] index`: Element index. -Returns an indexed property or array element as a [`Napi::Value`](value.md). +Returns a [`Napi::Object::PropertyLValue`](propertylvalue.md) or sets an +indexed property or array element. + +### begin() + +```cpp +Napi::Object::iterator Napi::Object::begin() const; +``` + +Returns a constant iterator to the beginning of the object. + +```cpp +Napi::Object::iterator Napi::Object::begin(); +``` + +Returns a non constant iterator to the beginning of the object. + +### end() + +```cpp +Napi::Object::iterator Napi::Object::end() const; +``` + +Returns a constant iterator to the end of the object. + +```cpp +Napi::Object::iterator Napi::Object::end(); +``` + +Returns a non constant iterator to the end of the object. + +## Iterator + +Iterators expose an `std::pair<...>`, where the `first` property is a +[`Napi::Value`](value.md) that holds the currently iterated key and the +`second` property is a [`Napi::Object::PropertyLValue`](propertylvalue.md) that +holds the currently iterated value. Iterators are only available if C++ +exceptions are enabled (by defining `NAPI_CPP_EXCEPTIONS` during the build). + +### Constant Iterator + +In constant iterators, the iterated values are immutable. + +#### operator++() + +```cpp +inline Napi::Object::const_iterator& Napi::Object::const_iterator::operator++(); +``` + +Moves the iterator one step forward. + +#### operator== + +```cpp +inline bool Napi::Object::const_iterator::operator==(const Napi::Object::const_iterator& other) const; +``` +- `[in] other`: Another iterator to compare the current iterator to. + +Returns whether both iterators are at the same index. + +#### operator!= + +```cpp +inline bool Napi::Object::const_iterator::operator!=(const Napi::Object::const_iterator& other) const; +``` +- `[in] other`: Another iterator to compare the current iterator to. + +Returns whether both iterators are at different indices. + +#### operator*() + +```cpp +inline const std::pair> Napi::Object::const_iterator::operator*() const; +``` + +Returns the currently iterated key and value. + +#### Example +```cpp +Value Sum(const CallbackInfo& info) { + Object object = info[0].As(); + int64_t sum = 0; + + for (const auto& e : object) { + sum += static_cast(e.second).As().Int64Value(); + } + + return Number::New(info.Env(), sum); +} +``` + +### Non Constant Iterator + +In non constant iterators, the iterated values are mutable. + +#### operator++() + +```cpp +inline Napi::Object::iterator& Napi::Object::iterator::operator++(); +``` + +Moves the iterator one step forward. + +#### operator== + +```cpp +inline bool Napi::Object::iterator::operator==(const Napi::Object::iterator& other) const; +``` +- `[in] other`: Another iterator to compare the current iterator to. + +Returns whether both iterators are at the same index. + +#### operator!= + +```cpp +inline bool Napi::Object::iterator::operator!=(const Napi::Object::iterator& other) const; +``` +- `[in] other`: Another iterator to compare the current iterator to. + +Returns whether both iterators are at different indices. + +#### operator*() + +```cpp +inline std::pair> Napi::Object::iterator::operator*(); +``` + +Returns the currently iterated key and value. + +#### Example +```cpp +void Increment(const CallbackInfo& info) { + Env env = info.Env(); + Object object = info[0].As(); + + for (auto e : object) { + int64_t value = static_cast(e.second).As().Int64Value(); + ++value; + e.second = Napi::Number::New(env, value); + } +} +``` -[`Napi::Value`]: ./value.md +[`Napi::TypeTaggable`]: ./type_taggable.md +[`Napi::Value::From`]: ./value.md#from diff --git a/doc/object_reference.md b/doc/object_reference.md index f2d8905a8..1ee697980 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -75,13 +75,13 @@ Napi::ObjectReference::ObjectReference(napi_env env, napi_value value); * `[in] env`: The `napi_env` environment in which to construct the `Napi::ObjectReference` object. -* `[in] value`: The N-API primitive value to be held by the `Napi::ObjectReference`. +* `[in] value`: The Node-API primitive value to be held by the `Napi::ObjectReference`. Returns the newly created reference. ### Set ```cpp -void Napi::ObjectReference::Set(___ key, ___ value); +bool Napi::ObjectReference::Set(___ key, ___ value); ``` * `[in] key`: The name for the property being assigned. @@ -103,7 +103,7 @@ The `value` can be any of the following types: ### Get ```cpp -Napi::Value Napi::ObjectReference::Get(___ key); +Napi::Value Napi::ObjectReference::Get(___ key) const; ``` * `[in] key`: The name of the property to return the value for. diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 7b8fc9c28..40fb3bf12 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -16,6 +16,10 @@ be directly invoked from JavaScript. The **wrap** word refers to a way of grouping methods and state of the class because it will be necessary write custom code to bridge each of your C++ class methods. +**Caution:** When the JavaScript object is garbage collected, the call to the +C++ destructor may be deferred until a later time. Within that period, +`Value()` will return an empty value. + ## Example ```cpp @@ -36,9 +40,9 @@ class Example : public Napi::ObjectWrap { Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { // This method is used to hook the accessor and method callbacks Napi::Function func = DefineClass(env, "Example", { - InstanceMethod<&Example::GetValue>("GetValue"), - InstanceMethod<&Example::SetValue>("SetValue"), - StaticMethod<&Example::CreateNewItem>("CreateNewItem"), + InstanceMethod<&Example::GetValue>("GetValue", static_cast(napi_writable | napi_configurable)), + InstanceMethod<&Example::SetValue>("SetValue", static_cast(napi_writable | napi_configurable)), + StaticMethod<&Example::CreateNewItem>("CreateNewItem", static_cast(napi_writable | napi_configurable)), }); Napi::FunctionReference* constructor = new Napi::FunctionReference(); @@ -160,7 +164,7 @@ static T* Napi::ObjectWrap::Unwrap(Napi::Object wrapper); * `[in] wrapper`: The JavaScript object that wraps the native instance. Returns a native instance wrapped in a JavaScript object. Given the -Napi:Object, this allows a method to get a pointer to the wrapped +`Napi::Object`, this allows a method to get a pointer to the wrapped C++ object and then reference fields, call methods, etc. within that class. In many cases calling Unwrap is not required, as methods can use the `this` field for ObjectWrap when running in a method on a @@ -212,11 +216,49 @@ property of the `Napi::CallbackInfo`. Returns a `Napi::Function` representing the constructor function for the class. +### OnCalledAsFunction + +Provides an opportunity to customize the behavior when a `Napi::ObjectWrap` +class is called from JavaScript as a function (without the **new** operator). + +The default behavior in this scenario is to throw a `Napi::TypeError` with the +message `Class constructors cannot be invoked without 'new'`. Define this +public method on your derived class to override that behavior. + +For example, you could internally re-call the JavaScript contstructor _with_ +the **new** operator (via +`Napi::Function::New(const std::vector &args)`), and return the +resulting object. Or you might do something else entirely, such as the way +[`Date()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#constructor) +produces a string when called as a function. + +```cpp +static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); +``` + +- `[in] callbackInfo`: The object representing the components of the JavaScript +request being made. + +### Finalize + +Provides an opportunity to run cleanup code that only utilizes basic Node APIs, if any. +Override to implement. See [Finalization][] for more details. + +```cpp +virtual void Finalize(Napi::BasicEnv env); +``` + +- `[in] env`: `Napi::Env`. + ### Finalize -Provides an opportunity to run cleanup code that requires access to the -`Napi::Env` before the wrapped native object instance is freed. Override to -implement. +Provides an opportunity to run cleanup code that utilizes non-basic Node APIs. +Override to implement. + +*NOTE*: Defining this method causes the deletion of the underlying `T* data` to +be postponed until _after_ the garbage collection cycle. Since an `Napi::Env` +has access to non-basic Node APIs, it cannot run in the same current tick as the +garbage collector. ```cpp virtual void Finalize(Napi::Env env); @@ -284,7 +326,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, void* data = nullptr); ``` -- `[in] name`: Napi:Symbol that represents the name of a static +- `[in] name`: Napi::Symbol that represents the name of a static method for the class. - `[in] method`: The native function that represents a static method of a JavaScript class. @@ -308,7 +350,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, ``` method for the class. -- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] name`: Napi::Symbol that represents the name of a static. - `[in] method`: The native function that represents a static method of a JavaScript class. - `[in] attributes`: The attributes associated with a particular property. @@ -380,7 +422,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, - `[in] method`: The native function that represents a static method of a JavaScript class. -- `[in] name`: Napi:Symbol that represents the name of a static +- `[in] name`: Napi::Symbol that represents the name of a static method for the class. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. @@ -403,7 +445,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, - `[in] method`: The native function that represents a static method of a JavaScript class. -- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] name`: Napi::Symbol that represents the name of a static. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. - `[in] data`: User-provided data passed into method when it is invoked. @@ -452,7 +494,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, void* data = nullptr); ``` -- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] name`: Napi::Symbol that represents the name of a static accessor. - `[in] getter`: The native function to call when a get access to the property of a JavaScript class is performed. - `[in] setter`: The native function to call when a set access to the property @@ -508,7 +550,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, of a JavaScript class is performed. - `[in] setter`: The native function to call when a set access to the property of a JavaScript class is performed. -- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] name`: Napi::Symbol that represents the name of a static accessor. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. - `[in] data`: User-provided data passed into getter or setter when @@ -559,3 +601,4 @@ Returns `Napi::PropertyDescriptor` object that represents an static value property of a JavaScript class [`Napi::InstanceWrap`]: ./instance_wrap.md +[Finalization]: ./finalization.md diff --git a/doc/prebuild_tools.md b/doc/prebuild_tools.md index ac1273812..4f1041aab 100644 --- a/doc/prebuild_tools.md +++ b/doc/prebuild_tools.md @@ -9,7 +9,7 @@ possible to distribute the native add-on in pre-built form for different platfor and architectures. The prebuild tools help to create and distribute the pre-built form of a native add-on. -The following list report known tools that are compatible with **N-API**: +The following list report known tools that are compatible with **Node-API**: - **[node-pre-gyp](https://www.npmjs.com/package/node-pre-gyp)** - **[prebuild](https://www.npmjs.com/package/prebuild)** diff --git a/doc/promises.md b/doc/promises.md index fd32c17d0..b4ab83389 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -63,7 +63,7 @@ void Napi::Promise::Deferred::Resolve(napi_value value) const; Resolves the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to resolve the `Napi::Promise`. +* `[in] value`: The Node-API primitive value with which to resolve the `Napi::Promise`. ### Reject @@ -73,7 +73,58 @@ void Napi::Promise::Deferred::Reject(napi_value value) const; Rejects the Promise object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to reject the `Napi::Promise`. +* `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`. +## Promise Methods + +### Then + +```cpp +Napi::Promise Napi::Promise::Then(napi_value onFulfilled) const; +Napi::Promise Napi::Promise::Then(const Function& onFulfilled) const; +``` + +Attaches a fulfillment handler to the promise and returns a new promise. + +**Parameters:** +* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is fulfilled. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled. + +**Returns:** A new `Napi::Promise` that resolves or rejects based on the handler's result. + +### Then + +```cpp +Napi::Promise Napi::Promise::Then(napi_value onFulfilled, napi_value onRejected) const; +Napi::Promise Napi::Promise::Then(const Function& onFulfilled, + const Function& onRejected) const; +``` + +Attaches a fulfillment and rejection handlers to the promise and returns a new promise. + +**Parameters:** +* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is fulfilled. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled. +* `[in] onRejected` (optional): The rejection handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is rejected. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected. + +### Catch +```cpp +Napi::Promise Napi::Promise::Catch(napi_value onRejected) const; +Napi::Promise Napi::Promise::Catch(const Function& onRejected) const; +``` + +Attaches a rejection handler to the promise and returns a new promise. + +**Parameters:** +* `[in] onRejected`: The rejection handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is rejected. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected. + +**Returns:** A new `Napi::Promise` that handles rejection cases. [`Napi::Object`]: ./object.md +[`Napi::Function`]: ./function.md diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 9995766e4..571cff4fd 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -50,7 +50,7 @@ Void Init(Env env) { ### PropertyDescriptor::GetterCallback ```cpp -typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); +using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); ``` This is the signature of a getter function to be passed as a template parameter @@ -59,7 +59,7 @@ to `PropertyDescriptor::Accessor`. ### PropertyDescriptor::SetterCallback ```cpp -typedef void (*SetterCallback)(const Napi::CallbackInfo& info); +using SetterCallback = void (*)(const Napi::CallbackInfo& info); ``` This is the signature of a setter function to be passed as a template parameter @@ -138,7 +138,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( @@ -186,7 +186,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( @@ -220,7 +220,7 @@ The name of the property can be any of the following types: static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, Callable cb, napi_property_attributes attributes = napi_default, - void *data = nullptr); + void *data = nullptr); ``` * `[in] name`: The name of the Callable function. @@ -236,7 +236,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( @@ -244,7 +244,7 @@ static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( ___ name, Callable cb, napi_property_attributes attributes = napi_default, - void *data = nullptr); + void *data = nullptr); ``` * `[in] env`: The environment in which to create this accessor. @@ -282,5 +282,5 @@ The name of the property can be any of the following types: - napi\_writable, - napi\_enumerable, - napi\_configurable -For more information on the flags and on napi\_property\_attributes, please read the documentation [here](https://github.com/nodejs/node/blob/master/doc/api/n-api.md#napi_property_attributes). +For more information on the flags and on napi\_property\_attributes, please read the documentation [here](https://github.com/nodejs/node/blob/HEAD/doc/api/n-api.md#napi_property_attributes). diff --git a/doc/propertylvalue.md b/doc/propertylvalue.md new file mode 100644 index 000000000..a41a3ce61 --- /dev/null +++ b/doc/propertylvalue.md @@ -0,0 +1,50 @@ +# PropertyLValue + +The `Napi::Object::PropertyLValue` class is a helper class provided by +`Napi::Object` to allow more intuitive assignment of properties. + +## Example +```cpp +#include + +using namespace Napi; + +Void Init(Env env) { + // Create a new instance + Object obj = Object::New(env); + + // Assign a value to a property. + obj["hello"] = "world"; +} +``` + +In the above example, `obj["hello"]` returns a `Napi::Object::PropertyLValue` +whose `operator=()` method accepts a string which will become the value of the +"hello" property of the newly created object. + +In general, `obj[key] = value` is the equivalent of `obj.Set(key, value)`, where +the types of `key` and `value` are all those supported by +[`Napi::Object::Set()`](object.md#set). + +## Methods + +### operator Value() + +```cpp +operator Value() const; +``` + +Implicitly casts this `Napi::Object::PropertyLValue` to a `Napi::Value`. + +### operator =() + +```cpp +template +PropertyLValue& operator =(ValueType value); +``` + +* `[in] value` a value to assign to the property referred to by the + `Napi::Object::PropertyLValue`. The type of the value is one of the types + supported by the second parameter of [`Napi::Object::Set()`](object.md#set). + +Returns a self-reference. diff --git a/doc/reference.md b/doc/reference.md index 108c009bb..0420990e7 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -4,7 +4,7 @@ Holds a counted reference to a [`Napi::Value`](value.md) object; initially a wea The referenced `Napi::Value` is not immediately destroyed when the reference count is zero; it is merely then eligible for garbage-collection if there are no other references to the `Napi::Value`. -`Napi::Reference` objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. +`Napi::Reference` objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. Avoid using this if at all possible. The following classes inherit, either directly or indirectly, from `Napi::Reference`: @@ -40,7 +40,7 @@ Napi::Reference::Reference(napi_env env, napi_value value); * `[in] env`: The `napi_env` environment in which to construct the `Napi::Reference` object. -* `[in] value`: The N-API primitive value to be held by the `Napi::Reference`. +* `[in] value`: The Node-API primitive value to be held by the `Napi::Reference`. ### Env @@ -69,7 +69,7 @@ Returns the value held by the `Napi::Reference`. ### Ref ```cpp -uint32_t Napi::Reference::Ref(); +uint32_t Napi::Reference::Ref() const; ``` Increments the reference count for the `Napi::Reference` and returns the resulting reference count. Throws an error if the increment fails. @@ -77,7 +77,7 @@ Increments the reference count for the `Napi::Reference` and returns the resulti ### Unref ```cpp -uint32_t Napi::Reference::Unref(); +uint32_t Napi::Reference::Unref() const; ``` Decrements the reference count for the `Napi::Reference` and returns the resulting reference count. Throws an error if the decrement fails. @@ -109,3 +109,5 @@ void Napi::Reference::SuppressDestruct(); ``` Call this method on a `Napi::Reference` that is declared as static data to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. + + Avoid using this if at all possible. If you do need to use static data, **MAKE SURE** to warn your users that your addon is **NOT** threadsafe. diff --git a/doc/setup.md b/doc/setup.md index 5ba5302de..b3b7effc6 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -2,7 +2,7 @@ ## Prerequisites -Before starting to use **N-API** you need to assure you have the following +Before starting to use **Node-API** you need to assure you have the following prerequisites: * **Node.JS** see: [Installing Node.js](https://nodejs.org/) @@ -13,69 +13,103 @@ prerequisites: ## Installation and usage -To use **N-API** in a native module: +To use **Node-API** in a native module: 1. Add a dependency on this package to `package.json`: -```json - "dependencies": { - "node-addon-api": "*", - } -``` + ```json + "dependencies": { + "node-addon-api": "*", + } + ``` - 2. Reference this package's include directory and gyp file in `binding.gyp`: - -```gyp - 'include_dirs': ["()` methods. + ### ElementSize ```cpp diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index f0abbd125..4ced5841c 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -11,14 +11,14 @@ classes. The common JavaScript `TypedArray` types are pre-defined for each of use: ```cpp -typedef Napi::TypedArrayOf Int8Array; -typedef Napi::TypedArrayOf Uint8Array; -typedef Napi::TypedArrayOf Int16Array; -typedef Napi::TypedArrayOf Uint16Array; -typedef Napi::TypedArrayOf Int32Array; -typedef Napi::TypedArrayOf Uint32Array; -typedef Napi::TypedArrayOf Float32Array; -typedef Napi::TypedArrayOf Float64Array; +using Int8Array = Napi::TypedArrayOf; +using Uint8Array = Napi::TypedArrayOf; +using Int16Array = Napi::TypedArrayOf; +using Uint16Array = Napi::TypedArrayOf; +using Int32Array = Napi::TypedArrayOf; +using Uint32Array = Napi::TypedArrayOf; +using Float32Array = Napi::TypedArrayOf; +using Float64Array = Napi::TypedArrayOf; ``` The one exception is the `Uint8ClampedArray` which requires explicit @@ -77,6 +77,34 @@ static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, Returns a new `Napi::TypedArrayOf` instance. +### New + +Wraps the provided `Napi::SharedArrayBuffer` into a new `Napi::TypedArray` instance. + +The array `type` parameter can normally be omitted (because it is inferred from +the template parameter `T`), except when creating a "clamped" array. + +```cpp +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. +- `[in] elementLength`: The length to array, in elements. +- `[in] arrayBuffer`: The backing `Napi::SharedArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `Napi::SharedArrayBuffer` where the array starts, + in bytes. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `Napi::TypedArrayOf` instance. + +**NOTE**: The support for this overload of `Napi::TypedArrayOf::New()` is only +available when using `NAPI_EXPERIMENTAL` and building against Node.js headers +that supports this feature. + ### Constructor Initializes an empty instance of the `Napi::TypedArrayOf` class. diff --git a/doc/typed_threadsafe_function.md b/doc/typed_threadsafe_function.md index e0d29807f..74d3cc2ed 100644 --- a/doc/typed_threadsafe_function.md +++ b/doc/typed_threadsafe_function.md @@ -73,7 +73,7 @@ New(napi_env env, - `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. It - can be retreived via `GetContext()`. + can be retrieved via `GetContext()`. - `[optional] finalizeCallback`: Function to call when the `TypedThreadSafeFunction` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It @@ -87,15 +87,15 @@ New(napi_env env, Returns a non-empty `Napi::TypedThreadSafeFunction` instance. -Depending on the targetted `NAPI_VERSION`, the API has different implementations +Depending on the targeted `NAPI_VERSION`, the API has different implementations for `CallbackType callback`. -When targetting version 4, `callback` may be: +When targeting version 4, `callback` may be: - of type `const Function&` - not provided as a parameter, in which case the API creates a new no-op `Function` -When targetting version 5+, `callback` may be: +When targeting version 5+, `callback` may be: - of type `const Function&` - of type `std::nullptr_t` - not provided as a parameter, in which case the API passes `std::nullptr` @@ -124,13 +124,13 @@ has undefined results in the current thread, as the thread-safe function may have been destroyed. ```cpp -napi_status Napi::TypedThreadSafeFunction::Release() +napi_status Napi::TypedThreadSafeFunction::Release() const ``` Returns one of: - `napi_ok`: The thread-safe function has been successfully released. - `napi_invalid_arg`: The thread-safe function's thread-count is zero. -- `napi_generic_failure`: A generic error occurred when attemping to release the +- `napi_generic_failure`: A generic error occurred when attempting to release the thread-safe function. ### Abort @@ -146,13 +146,13 @@ function call a thread must make no further use of the thread-safe function because it is no longer guaranteed to be allocated. ```cpp -napi_status Napi::TypedThreadSafeFunction::Abort() +napi_status Napi::TypedThreadSafeFunction::Abort() const ``` Returns one of: - `napi_ok`: The thread-safe function has been successfully aborted. - `napi_invalid_arg`: The thread-safe function's thread-count is zero. -- `napi_generic_failure`: A generic error occurred when attemping to abort the +- `napi_generic_failure`: A generic error occurred when attempting to abort the thread-safe function. ### BlockingCall / NonBlockingCall @@ -180,7 +180,7 @@ Returns one of: - `napi_closing`: The thread-safe function is aborted and no further calls can be made. - `napi_invalid_arg`: The thread-safe function is closed. -- `napi_generic_failure`: A generic error occurred when attemping to add to the +- `napi_generic_failure`: A generic error occurred when attempting to add to the queue. @@ -215,8 +215,7 @@ Value Start(const CallbackInfo &info) { int count = info[1].As().Int32Value(); - // Create a new context set to the the receiver (ie, `this`) of the function - // call + // Create a new context set to the receiver (ie, `this`) of the function call Context *context = new Reference(Persistent(info.This())); // Create a ThreadSafeFunction @@ -263,7 +262,7 @@ void CallJs(Napi::Env env, Function callback, Context *context, // Is the JavaScript environment still available to call into, eg. the TSFN is // not aborted if (env != nullptr) { - // On N-API 5+, the `callback` parameter is optional; however, this example + // On Node-API 5+, the `callback` parameter is optional; however, this example // does ensure a callback is provided. if (callback != nullptr) { callback.Call(context->Value(), {Number::New(env, *data)}); diff --git a/doc/value.md b/doc/value.md index ca9e3d2c9..f61a36ecf 100644 --- a/doc/value.md +++ b/doc/value.md @@ -3,9 +3,9 @@ `Napi::Value` is the C++ manifestation of a JavaScript value. It is the base class upon which other JavaScript values such as `Napi::Number`, `Napi::Boolean`, `Napi::String`, and `Napi::Object` are based. It represents a -JavaScript value of an unknown type. It is a thin wrapper around the N-API +JavaScript value of an unknown type. It is a thin wrapper around the Node-API datatype `napi_value`. Methods on this class can be used to check the JavaScript -type of the underlying N-API `napi_value` and also to convert to C++ types. +type of the underlying Node-API `napi_value` and also to convert to C++ types. ## Constructors @@ -45,7 +45,7 @@ value` may be any of: Napi::Value::operator napi_value() const; ``` -Returns the underlying N-API `napi_value`. If the instance is _empty_, this +Returns the underlying Node-API `napi_value`. If the instance is _empty_, this returns `nullptr`. ### operator == @@ -78,7 +78,26 @@ Casts to another type of `Napi::Value`, when the actual type is known or assumed. This conversion does not coerce the type. Calling any methods inappropriate for -the actual value type will throw `Napi::Error`. +the actual value type will throw `Napi::Error`. When C++ exceptions are +disabled, the thrown error will not be reflected before control returns to +JavaScript. + +In order to enforce expected type, use `Napi::Value::Is*()` methods to check +the type before calling `Napi::Value::As()`, or compile with definition +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` to enforce type checks. + +### UnsafeAs + +```cpp +template T Napi::Value::UnsafeAs() const; +``` + +Casts to another type of `Napi::Value`, when the actual type is known or +assumed. + +This conversion does not coerce the type. This does not check the type even if +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined. This indicates intentional +unsafe type cast. Use `Napi::Value::As()` if possible. ### Env @@ -98,10 +117,10 @@ static Napi::Value Napi::Value::From(napi_env env, const T& value); - `[in] env`: The `napi_env` environment in which to create the `Napi::Value` object. -- `[in] value`: The N-API primitive value from which to create the `Napi::Value` +- `[in] value`: The Node-API primitive value from which to create the `Napi::Value` object. -Returns a `Napi::Value` object from an N-API primitive value. +Returns a `Napi::Value` object from an Node-API primitive value. This method is used to convert from a C++ type to a JavaScript value. Here, `value` may be any of: @@ -135,6 +154,15 @@ bool Napi::Value::IsArrayBuffer() const; Returns `true` if the underlying value is a JavaScript `Napi::ArrayBuffer` or `false` otherwise. +### IsBigInt + +```cpp +bool Napi::Value::IsBigInt() const; +``` + +Returns `true` if the underlying value is a JavaScript `Napi::BigInt` or `false` +otherwise. + ### IsBoolean ```cpp @@ -192,7 +220,7 @@ Thus, when C++ exceptions are not being used, callers should check the result of bool Napi::Value::IsExternal() const; ``` -Returns `true` if the underlying value is a N-API external object or `false` +Returns `true` if the underlying value is a Node-API external object or `false` otherwise. ### IsFunction @@ -240,6 +268,19 @@ bool Napi::Value::IsPromise() const; Returns `true` if the underlying value is a JavaScript `Napi::Promise` or `false` otherwise. +### IsSharedArrayBuffer + +```cpp +bool Napi::Value::IsSharedArrayBuffer() const; +``` + +Returns `true` if the underlying value is a JavaScript +`Napi::IsSharedArrayBuffer` or `false` otherwise. + +**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using +`NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ### IsString ```cpp diff --git a/doc/version_management.md b/doc/version_management.md index c67ca450e..b289f1b1d 100644 --- a/doc/version_management.md +++ b/doc/version_management.md @@ -1,22 +1,22 @@ # VersionManagement The `Napi::VersionManagement` class contains methods that allow information -to be retrieved about the version of N-API and Node.js. In some cases it is +to be retrieved about the version of Node-API and Node.js. In some cases it is important to make decisions based on different versions of the system. ## Methods ### GetNapiVersion -Retrieves the highest N-API version supported by Node.js runtime. +Retrieves the highest Node-API version supported by Node.js runtime. ```cpp -static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); +static uint32_t Napi::VersionManagement::GetNapiVersion(Napi::BasicEnv env); ``` - `[in] env`: The environment in which the API is invoked under. -Returns the highest N-API version supported by Node.js runtime. +Returns the highest Node-API version supported by Node.js runtime. ### GetNodeVersion @@ -34,7 +34,7 @@ typedef struct { ```` ```cpp -static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Env env); +static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Napi::BasicEnv env); ``` - `[in] env`: The environment in which the API is invoked under. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..d02c6f529 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = require('neostandard')({ + semi: true, +}); diff --git a/except.gypi b/except.gypi index 1f295d10a..e2fb2c5a4 100644 --- a/except.gypi +++ b/except.gypi @@ -2,15 +2,24 @@ 'defines': [ 'NAPI_CPP_EXCEPTIONS' ], 'cflags!': [ '-fno-exceptions' ], 'cflags_cc!': [ '-fno-exceptions' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'ExceptionHandling': 1, - 'EnablePREfast': 'true', - }, - }, - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', - }, + 'conditions': [ + ["OS=='win'", { + "defines": [ + "_HAS_EXCEPTIONS=1" + ], + "msvs_settings": { + "VCCLCompilerTool": { + "ExceptionHandling": 1, + 'EnablePREfast': 'true', + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + }, + }], + ], } diff --git a/index.js b/index.js index 2fa51c583..e235cc3e9 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,14 @@ const path = require('path'); +const { version } = require('./package.json'); -const include_dir = path.relative('.', __dirname); +const includeDir = path.relative('.', __dirname); module.exports = { include: `"${__dirname}"`, // deprecated, can be removed as part of 4.0.0 - include_dir, - gyp: path.join(include_dir, 'node_api.gyp:nothing'), + include_dir: includeDir, + gyp: path.join(includeDir, 'node_api.gyp:nothing'), // deprecated. + targets: path.join(includeDir, 'node_addon_api.gyp'), + version, isNodeApiBuiltin: true, needsFlag: false }; diff --git a/napi-inl.deprecated.h b/napi-inl.deprecated.h index f19aca76b..3ddbb2efa 100644 --- a/napi-inl.deprecated.h +++ b/napi-inl.deprecated.h @@ -6,187 +6,181 @@ //////////////////////////////////////////////////////////////////////////////// template -inline PropertyDescriptor -PropertyDescriptor::Accessor(const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { - typedef details::CallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, nullptr }); - - return PropertyDescriptor({ - utf8name, - nullptr, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { - typedef details::CallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, nullptr }); - - return PropertyDescriptor({ - nullptr, - name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, Getter getter, napi_property_attributes attributes, void* data) { napi_value nameValue = name; return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { - typedef details::AccessorCallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, setter, nullptr }); - - return PropertyDescriptor({ - utf8name, - nullptr, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { - typedef details::AccessorCallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, setter, nullptr }); - - return PropertyDescriptor({ - nullptr, - name, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); + return PropertyDescriptor::Accessor( + nameValue, getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - - return PropertyDescriptor({ - utf8name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { return Function(utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; +inline PropertyDescriptor PropertyDescriptor::Function( + napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - - return PropertyDescriptor({ - nullptr, - name, - CbData::Wrapper, - nullptr, - nullptr, - nullptr, - attributes, - callbackData - }); + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Function(Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Function( + Name name, Callable cb, napi_property_attributes attributes, void* data) { napi_value nameValue = name; return PropertyDescriptor::Function(nameValue, cb, attributes, data); } -#endif // !SRC_NAPI_INL_DEPRECATED_H_ +#endif // !SRC_NAPI_INL_DEPRECATED_H_ diff --git a/napi-inl.h b/napi-inl.h index 4e8e8ef11..ec63ffeee 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2,59 +2,78 @@ #define SRC_NAPI_INL_H_ //////////////////////////////////////////////////////////////////////////////// -// N-API C++ Wrapper Classes +// Node-API C++ Wrapper Classes // -// Inline header-only implementations for "N-API" ABI-stable C APIs for Node.js. +// Inline header-only implementations for "Node-API" ABI-stable C APIs for +// Node.js. //////////////////////////////////////////////////////////////////////////////// // Note: Do not include this file directly! Include "napi.h" instead. +// This should be a no-op and is intended for better IDE integration. +#include "napi.h" #include +#include #include +#if NAPI_HAS_THREADS #include +#endif // NAPI_HAS_THREADS +#include #include +#include + +#if defined(__clang__) || defined(__GNUC__) +#define NAPI_NO_SANITIZE_VPTR __attribute__((no_sanitize("vptr"))) +#else +#define NAPI_NO_SANITIZE_VPTR +#endif namespace Napi { -// Helpers to handle functions exposed from C++. +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Helpers to handle functions exposed from C++ and internal constants. namespace details { +// New napi_status constants not yet available in all supported versions of +// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h. +constexpr int napi_no_external_buffers_allowed = 22; + +template +inline void default_basic_finalizer(node_addon_api_basic_env /*env*/, + void* data, + void* /*hint*/) { + delete static_cast(data); +} + // Attach a data item to an object and delete it when the object gets // garbage-collected. // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes // available on all supported versions of Node.js. -template -static inline napi_status AttachData(napi_env env, - napi_value obj, - FreeType* data, - napi_finalize finalizer = nullptr, - void* hint = nullptr) { +template < + typename FreeType, + node_addon_api_basic_finalize finalizer = default_basic_finalizer> +inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data, + void* hint = nullptr) { napi_status status; - if (finalizer == nullptr) { - finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) { - delete static_cast(data); - }; - } #if (NAPI_VERSION < 5) napi_value symbol, external; status = napi_create_symbol(env, nullptr, &symbol); if (status == napi_ok) { - status = napi_create_external(env, - data, - finalizer, - hint, - &external); + status = napi_create_external(env, data, finalizer, hint, &external); if (status == napi_ok) { - napi_property_descriptor desc = { - nullptr, - symbol, - nullptr, - nullptr, - nullptr, - external, - napi_default, - nullptr - }; + napi_property_descriptor desc = {nullptr, + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr}; status = napi_define_properties(env, obj, 1, &desc); } } @@ -67,47 +86,91 @@ static inline napi_status AttachData(napi_env env, // For use in JS to C++ callback wrappers to catch any Napi::Error exceptions // and rethrow them as JavaScript exceptions before returning from the callback. template -inline napi_value WrapCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline napi_value WrapCallback(napi_env env, Callable callback) { +#else +inline napi_value WrapCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { return callback(); } catch (const Error& e) { e.ThrowAsJavaScriptException(); return nullptr; } -#else // NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + return nullptr; + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + return nullptr; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else // NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are disabled, errors are immediately thrown as JS // exceptions, so there is no need to catch and rethrow them here. return callback(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } // For use in JS to C++ void callback wrappers to catch any Napi::Error -// exceptions and rethrow them as JavaScript exceptions before returning from the -// callback. +// exceptions and rethrow them as JavaScript exceptions before returning from +// the callback. template inline void WrapVoidCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { callback(); } catch (const Error& e) { e.ThrowAsJavaScriptException(); } -#else // NAPI_CPP_EXCEPTIONS +#else // NAPI_CPP_EXCEPTIONS // When C++ exceptions are disabled, errors are immediately thrown as JS // exceptions, so there is no need to catch and rethrow them here. callback(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NAPI_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch _any_ thrown exception +// and rethrow them as JavaScript exceptions before returning from the callback, +// wrapping in an Napi::Error as needed. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline void WrapVoidCallback(napi_env env, Callable callback) { +#else +inline void WrapVoidCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else + // When C++ exceptions are disabled, there is no need to catch and rethrow C++ + // exceptions. JS errors should be thrown with + // `Error::ThrowAsJavaScriptException`. + callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } template struct CallbackData { - static inline - napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = - static_cast(callbackInfo.Data()); + static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->callback(callbackInfo); }); @@ -119,12 +182,11 @@ struct CallbackData { template struct CallbackData { - static inline - napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = - static_cast(callbackInfo.Data()); + static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->callback(callbackInfo); return nullptr; @@ -136,9 +198,9 @@ struct CallbackData { }; template -static napi_value -TemplatedVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { +napi_value TemplatedVoidCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); Callback(cbInfo); return nullptr; @@ -146,53 +208,137 @@ TemplatedVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { } template -static napi_value -TemplatedCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { +napi_value TemplatedCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); - return Callback(cbInfo); + // MSVC requires to copy 'Callback' function pointer to a local variable + // before invoking it. + auto callback = Callback; + return callback(cbInfo); }); } template -static napi_value -TemplatedInstanceCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { +napi_value TemplatedInstanceCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); - return (instance->*UnwrapCallback)(cbInfo); + return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); }); } template -static napi_value -TemplatedInstanceVoidCallback(napi_env env, - napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { +napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) + NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); - (instance->*UnwrapCallback)(cbInfo); + if (instance) (instance->*UnwrapCallback)(cbInfo); return nullptr; }); } template struct FinalizeData { - static inline - void Wrapper(napi_env env, void* data, void* finalizeHint) noexcept { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { WrapVoidCallback([&] { FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(Env(env), static_cast(data)); + finalizeData->callback(env, static_cast(data)); delete finalizeData; }); } - static inline - void WrapperWithHint(napi_env env, void* data, void* finalizeHint) noexcept { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGC, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { WrapVoidCallback([&] { FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(Env(env), static_cast(data), finalizeData->hint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + + static inline void WrapperGCWithoutData(napi_env env, + void* /*data*/, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env); + delete finalizeData; + }); + } + + static inline void WrapperGC(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + + static inline void WrapperGCWithHint(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); delete finalizeData; }); } @@ -201,15 +347,15 @@ struct FinalizeData { Hint* hint; }; -#if (NAPI_VERSION > 3 && !defined(__wasm32__)) -template , - typename FinalizerDataType=void> +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +template , + typename FinalizerDataType = void> struct ThreadSafeFinalize { - static inline - void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) { - if (rawFinalizeData == nullptr) - return; + static inline void Wrapper(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); @@ -217,12 +363,10 @@ struct ThreadSafeFinalize { delete finalizeData; } - static inline - void FinalizeWrapperWithData(napi_env env, - void* rawFinalizeData, - void* /* rawContext */) { - if (rawFinalizeData == nullptr) - return; + static inline void FinalizeWrapperWithData(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); @@ -230,12 +374,10 @@ struct ThreadSafeFinalize { delete finalizeData; } - static inline - void FinalizeWrapperWithContext(napi_env env, - void* rawFinalizeData, - void* rawContext) { - if (rawFinalizeData == nullptr) - return; + static inline void FinalizeWrapperWithContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) return; ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); @@ -243,17 +385,14 @@ struct ThreadSafeFinalize { delete finalizeData; } - static inline - void FinalizeFinalizeWrapperWithDataAndContext(napi_env env, - void* rawFinalizeData, - void* rawContext) { - if (rawFinalizeData == nullptr) - return; + static inline void FinalizeFinalizeWrapperWithDataAndContext( + napi_env env, void* rawFinalizeData, void* rawContext) { + if (rawFinalizeData == nullptr) return; ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); - finalizeData->callback(Env(env), finalizeData->data, - static_cast(rawContext)); + finalizeData->callback( + Env(env), finalizeData->data, static_cast(rawContext)); delete finalizeData; } @@ -262,20 +401,27 @@ struct ThreadSafeFinalize { }; template -typename std::enable_if::type static inline CallJsWrapper( - napi_env env, napi_value jsCallback, void* context, void* data) { - call(env, - Function(env, jsCallback), - static_cast(context), - static_cast(data)); +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { + details::WrapVoidCallback(env, [&]() { + call(env, + Function(env, jsCallback), + static_cast(context), + static_cast(data)); + }); } template -typename std::enable_if::type static inline CallJsWrapper( - napi_env env, napi_value jsCallback, void* /*context*/, void* /*data*/) { - if (jsCallback != nullptr) { - Function(env, jsCallback).Call(0, nullptr); - } +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, + napi_value jsCallback, + void* /*context*/, + void* /*data*/) { + details::WrapVoidCallback(env, [&]() { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } + }); } #if NAPI_VERSION > 4 @@ -299,27 +445,27 @@ napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { return cb; } #endif // NAPI_VERSION > 4 -#endif // NAPI_VERSION > 3 && !defined(__wasm32__) +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS template struct AccessorCallbackData { - static inline - napi_value GetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + static inline napi_value GetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = - static_cast(callbackInfo.Data()); + static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->getterCallback(callbackInfo); }); } - static inline - napi_value SetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + static inline napi_value SetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = - static_cast(callbackInfo.Data()); + static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->setterCallback(callbackInfo); return nullptr; @@ -331,36 +477,74 @@ struct AccessorCallbackData { void* data; }; +// Debugging-purpose C++-style variant of sprintf(). +inline std::string StringFormat(const char* format, ...) { + std::string result; + va_list args; + va_start(args, format); + int len = vsnprintf(nullptr, 0, format, args); + result.resize(len); + vsnprintf(&result[0], len + 1, format, args); + va_end(args); + return result; +} + +template +class HasExtendedFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +template +class HasBasicFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + } // namespace details #ifndef NODE_ADDON_API_DISABLE_DEPRECATED -# include "napi-inl.deprecated.h" -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED +#include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED //////////////////////////////////////////////////////////////////////////////// // Module registration //////////////////////////////////////////////////////////////////////////////// // Register an add-on based on an initializer function. -#define NODE_API_MODULE(modname, regfunc) \ - napi_value __napi_ ## regfunc(napi_env env, \ - napi_value exports) { \ - return Napi::RegisterModule(env, exports, regfunc); \ - } \ - NAPI_MODULE(modname, __napi_ ## regfunc) +#define NODE_API_MODULE(modname, regfunc) \ + static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, regfunc); \ + } \ + NAPI_MODULE(modname, __napi_##regfunc) // Register an add-on based on a subclass of `Addon` with a custom Node.js // module name. -#define NODE_API_NAMED_ADDON(modname, classname) \ - static napi_value __napi_ ## classname(napi_env env, \ - napi_value exports) { \ - return Napi::RegisterModule(env, exports, &classname::Init); \ - } \ - NAPI_MODULE(modname, __napi_ ## classname) +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_##classname(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_##classname) // Register an add-on based on a subclass of `Addon` with the Node.js module // name given by node-gyp from the `target_name` in binding.gyp. -#define NODE_API_ADDON(classname) \ +#define NODE_API_ADDON(classname) \ NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) // Adapt the NAPI_MODULE registration function: @@ -369,23 +553,94 @@ struct AccessorCallbackData { inline napi_value RegisterModule(napi_env env, napi_value exports, ModuleRegisterCallback registerCallback) { - return details::WrapCallback([&] { - return napi_value(registerCallback(Napi::Env(env), - Napi::Object(env, exports))); + return details::WrapCallback(env, [&] { + return napi_value( + registerCallback(Napi::Env(env), Napi::Object(env, exports))); }); } //////////////////////////////////////////////////////////////////////////////// -// Env class +// Maybe class //////////////////////////////////////////////////////////////////////////////// -inline Env::Env(napi_env env) : _env(env) { +template +bool Maybe::IsNothing() const { + return !_has_value; } -inline Env::operator napi_env() const { +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + +//////////////////////////////////////////////////////////////////////////////// +// BasicEnv / Env class +//////////////////////////////////////////////////////////////////////////////// + +inline BasicEnv::BasicEnv(node_addon_api_basic_env env) : _env(env) {} + +inline BasicEnv::operator node_addon_api_basic_env() const { return _env; } +inline Env::Env(napi_env env) : BasicEnv(env) {} + +inline Env::operator napi_env() const { + return const_cast(_env); +} + inline Object Env::Global() const { napi_value value; napi_status status = napi_get_global(*this, &value); @@ -409,99 +664,134 @@ inline Value Env::Null() const { inline bool Env::IsExceptionPending() const { bool result; - napi_status status = napi_is_exception_pending(_env, &result); - if (status != napi_ok) result = false; // Checking for a pending exception shouldn't throw. + napi_status status = napi_is_exception_pending(*this, &result); + if (status != napi_ok) + result = false; // Checking for a pending exception shouldn't throw. return result; } -inline Error Env::GetAndClearPendingException() { +inline Error Env::GetAndClearPendingException() const { napi_value value; - napi_status status = napi_get_and_clear_last_exception(_env, &value); + napi_status status = napi_get_and_clear_last_exception(*this, &value); if (status != napi_ok) { // Don't throw another exception when failing to get the exception! return Error(); } - return Error(_env, value); + return Error(*this, value); } -inline Value Env::RunScript(const char* utf8script) { - String script = String::New(_env, utf8script); +inline MaybeOrValue Env::RunScript(const char* utf8script) const { + String script = String::New(*this, utf8script); return RunScript(script); } -inline Value Env::RunScript(const std::string& utf8script) { +inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { return RunScript(utf8script.c_str()); } -inline Value Env::RunScript(String script) { +inline MaybeOrValue Env::RunScript(String script) const { napi_value result; - napi_status status = napi_run_script(_env, script, &result); - NAPI_THROW_IF_FAILED(_env, status, Undefined()); - return Value(_env, result); + napi_status status = napi_run_script(*this, script, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + *this, status, Napi::Value(*this, result), Napi::Value); +} + +#if NAPI_VERSION > 2 +template +void BasicEnv::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(); + delete cleanupData; +} + +template +void BasicEnv::CleanupHook::WrapperWithArg(void* data) + NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(static_cast(cleanupData->arg)); + delete cleanupData; } +#endif // NAPI_VERSION > 2 #if NAPI_VERSION > 5 -template fini> -inline void Env::SetInstanceData(T* data) { - napi_status status = - napi_set_instance_data(_env, data, [](napi_env env, void* data, void*) { - fini(env, static_cast(data)); - }, nullptr); - NAPI_THROW_IF_FAILED_VOID(_env, status); +template fini> +inline void BasicEnv::SetInstanceData(T* data) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, + nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); } template fini> -inline void Env::SetInstanceData(DataType* data, HintType* hint) { - napi_status status = - napi_set_instance_data(_env, data, + Napi::BasicEnv::FinalizerWithHint fini> +inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const { + napi_status status = napi_set_instance_data( + _env, + data, [](napi_env env, void* data, void* hint) { fini(env, static_cast(data), static_cast(hint)); - }, hint); - NAPI_THROW_IF_FAILED_VOID(_env, status); + }, + hint); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); } template -inline T* Env::GetInstanceData() { +inline T* BasicEnv::GetInstanceData() const { void* data = nullptr; napi_status status = napi_get_instance_data(_env, &data); - NAPI_THROW_IF_FAILED(_env, status, nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetInstanceData", "invalid arguments"); return static_cast(data); } -template void Env::DefaultFini(Env, T* data) { +template +void BasicEnv::DefaultFini(Env, T* data) { delete data; } template -void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) { +void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) { delete data; } #endif // NAPI_VERSION > 5 +#if NAPI_VERSION > 8 +inline const char* BasicEnv::GetModuleFileName() const { + const char* result; + napi_status status = node_api_get_module_file_name(_env, &result); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetModuleFileName", "invalid arguments"); + return result; +} +#endif // NAPI_VERSION > 8 //////////////////////////////////////////////////////////////////////////////// // Value class //////////////////////////////////////////////////////////////////////////////// -inline Value::Value() : _env(nullptr), _value(nullptr) { -} +inline Value::Value() : _env(nullptr), _value(nullptr) {} -inline Value::Value(napi_env env, napi_value value) : _env(env), _value(value) { -} +inline Value::Value(napi_env env, napi_value value) + : _env(env), _value(value) {} inline Value::operator napi_value() const { return _value; } -inline bool Value::operator ==(const Value& other) const { +inline bool Value::operator==(const Value& other) const { return StrictEquals(other); } -inline bool Value::operator !=(const Value& other) const { - return !this->operator ==(other); +inline bool Value::operator!=(const Value& other) const { + return !this->operator==(other); } inline bool Value::StrictEquals(const Value& other) const { @@ -651,37 +941,63 @@ inline bool Value::IsExternal() const { return Type() == napi_external; } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +inline bool Value::IsSharedArrayBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = node_api_is_sharedarraybuffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + template inline T Value::As() const { +#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS + T::CheckCast(_env, _value); +#endif + return T(_env, _value); +} + +template +inline T Value::UnsafeAs() const { return T(_env, _value); } -inline Boolean Value::ToBoolean() const { +// static +inline void Value::CheckCast(napi_env /* env */, napi_value value) { + NAPI_CHECK(value != nullptr, "Value::CheckCast", "empty value"); +} + +inline MaybeOrValue Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Boolean()); - return Boolean(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); } -inline Number Value::ToNumber() const { +inline MaybeOrValue Value::ToNumber() const { napi_value result; napi_status status = napi_coerce_to_number(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Number()); - return Number(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); } -inline String Value::ToString() const { +inline MaybeOrValue Value::ToString() const { napi_value result; napi_status status = napi_coerce_to_string(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, String()); - return String(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); } -inline Object Value::ToObject() const { +inline MaybeOrValue Value::ToObject() const { napi_value result; napi_status status = napi_coerce_to_object(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -695,12 +1011,20 @@ inline Boolean Boolean::New(napi_env env, bool val) { return Boolean(env, value); } -inline Boolean::Boolean() : Napi::Value() { -} +inline void Boolean::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); -inline Boolean::Boolean(napi_env env, napi_value value) : Napi::Value(env, value) { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_boolean, "%d", "Boolean::CheckCast"); } +inline Boolean::Boolean() : Napi::Value() {} + +inline Boolean::Boolean(napi_env env, napi_value value) + : Napi::Value(env, value) {} + inline Boolean::operator bool() const { return Value(); } @@ -723,12 +1047,19 @@ inline Number Number::New(napi_env env, double val) { return Number(env, value); } -inline Number::Number() : Value() { -} +inline void Number::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); -inline Number::Number(napi_env env, napi_value value) : Value(env, value) { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_number, "%d", "Number::CheckCast"); } +inline Number::Number() : Value() {} + +inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} + inline Number::operator int32_t() const { return Int32Value(); } @@ -800,46 +1131,59 @@ inline BigInt BigInt::New(napi_env env, uint64_t val) { return BigInt(env, value); } -inline BigInt BigInt::New(napi_env env, int sign_bit, size_t word_count, const uint64_t* words) { +inline BigInt BigInt::New(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words) { napi_value value; - napi_status status = napi_create_bigint_words(env, sign_bit, word_count, words, &value); + napi_status status = + napi_create_bigint_words(env, sign_bit, word_count, words, &value); NAPI_THROW_IF_FAILED(env, status, BigInt()); return BigInt(env, value); } -inline BigInt::BigInt() : Value() { -} +inline void BigInt::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); -inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_bigint, "%d", "BigInt::CheckCast"); } +inline BigInt::BigInt() : Value() {} + +inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} + inline int64_t BigInt::Int64Value(bool* lossless) const { int64_t result; - napi_status status = napi_get_value_bigint_int64( - _env, _value, &result, lossless); + napi_status status = + napi_get_value_bigint_int64(_env, _value, &result, lossless); NAPI_THROW_IF_FAILED(_env, status, 0); return result; } inline uint64_t BigInt::Uint64Value(bool* lossless) const { uint64_t result; - napi_status status = napi_get_value_bigint_uint64( - _env, _value, &result, lossless); + napi_status status = + napi_get_value_bigint_uint64(_env, _value, &result, lossless); NAPI_THROW_IF_FAILED(_env, status, 0); return result; } inline size_t BigInt::WordCount() const { size_t word_count; - napi_status status = napi_get_value_bigint_words( - _env, _value, nullptr, &word_count, nullptr); + napi_status status = + napi_get_value_bigint_words(_env, _value, nullptr, &word_count, nullptr); NAPI_THROW_IF_FAILED(_env, status, 0); return word_count; } -inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) { - napi_status status = napi_get_value_bigint_words( - _env, _value, sign_bit, word_count, words); +inline void BigInt::ToWords(int* sign_bit, + size_t* word_count, + uint64_t* words) { + napi_status status = + napi_get_value_bigint_words(_env, _value, sign_bit, word_count, words); NAPI_THROW_IF_FAILED_VOID(_env, status); } #endif // NAPI_VERSION > 5 @@ -856,20 +1200,33 @@ inline Date Date::New(napi_env env, double val) { return Date(env, value); } -inline Date::Date() : Value() { +inline Date Date::New(napi_env env, std::chrono::system_clock::time_point tp) { + using namespace std::chrono; + auto ms = static_cast( + duration_cast(tp.time_since_epoch()).count()); + return Date::New(env, ms); } -inline Date::Date(napi_env env, napi_value value) : Value(env, value) { +inline void Date::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_date(env, value, &result); + NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); + NAPI_CHECK(result, "Date::CheckCast", "value is not date"); } +inline Date::Date() : Value() {} + +inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} + inline Date::operator double() const { return ValueOf(); } inline double Date::ValueOf() const { double result; - napi_status status = napi_get_date_value( - _env, _value, &result); + napi_status status = napi_get_date_value(_env, _value, &result); NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -878,12 +1235,21 @@ inline double Date::ValueOf() const { //////////////////////////////////////////////////////////////////////////////// // Name class //////////////////////////////////////////////////////////////////////////////// +inline void Name::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); -inline Name::Name() : Value() { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol, got %d.", + type); } -inline Name::Name(napi_env env, napi_value value) : Value(env, value) { -} +inline Name::Name() : Value() {} + +inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} //////////////////////////////////////////////////////////////////////////////// // String class @@ -897,16 +1263,34 @@ inline String String::New(napi_env env, const std::u16string& val) { return String::New(env, val.c_str(), val.size()); } +inline String String::New(napi_env env, std::string_view val) { + return String::New(env, val.data(), val.size()); +} + inline String String::New(napi_env env, const char* val) { + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } napi_value value; - napi_status status = napi_create_string_utf8(env, val, std::strlen(val), &value); + napi_status status = + napi_create_string_utf8(env, val, std::strlen(val), &value); NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } inline String String::New(napi_env env, const char16_t* val) { napi_value value; - napi_status status = napi_create_string_utf16(env, val, std::u16string(val).size(), &value); + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_status status = + napi_create_string_utf16(env, val, std::u16string(val).size(), &value); NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } @@ -925,12 +1309,19 @@ inline String String::New(napi_env env, const char16_t* val, size_t length) { return String(env, value); } -inline String::String() : Name() { -} +inline void String::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); -inline String::String(napi_env env, napi_value value) : Name(env, value) { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_string, "%d", "String::CheckCast"); } +inline String::String() : Name() {} + +inline String::String(napi_env env, napi_value value) : Name(env, value) {} + inline String::operator std::string() const { return Utf8Value(); } @@ -941,26 +1332,30 @@ inline String::operator std::u16string() const { inline std::string String::Utf8Value() const { size_t length; - napi_status status = napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); + napi_status status = + napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); NAPI_THROW_IF_FAILED(_env, status, ""); std::string value; value.reserve(length + 1); value.resize(length); - status = napi_get_value_string_utf8(_env, _value, &value[0], value.capacity(), nullptr); + status = napi_get_value_string_utf8( + _env, _value, &value[0], value.capacity(), nullptr); NAPI_THROW_IF_FAILED(_env, status, ""); return value; } inline std::u16string String::Utf16Value() const { size_t length; - napi_status status = napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); + napi_status status = + napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); std::u16string value; value.reserve(length + 1); value.resize(length); - status = napi_get_value_string_utf16(_env, _value, &value[0], value.capacity(), nullptr); + status = napi_get_value_string_utf16( + _env, _value, &value[0], value.capacity(), nullptr); NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); return value; } @@ -970,8 +1365,9 @@ inline std::u16string String::Utf16Value() const { //////////////////////////////////////////////////////////////////////////////// inline Symbol Symbol::New(napi_env env, const char* description) { - napi_value descriptionValue = description != nullptr ? - String::New(env, description) : static_cast(nullptr); + napi_value descriptionValue = description != nullptr + ? String::New(env, description) + : static_cast(nullptr); return Symbol::New(env, descriptionValue); } @@ -980,6 +1376,11 @@ inline Symbol Symbol::New(napi_env env, const std::string& description) { return Symbol::New(env, descriptionValue); } +inline Symbol Symbol::New(napi_env env, std::string_view description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::New(env, descriptionValue); +} + inline Symbol Symbol::New(napi_env env, String description) { napi_value descriptionValue = description; return Symbol::New(env, descriptionValue); @@ -992,16 +1393,85 @@ inline Symbol Symbol::New(napi_env env, napi_value description) { return Symbol(env, value); } -inline Symbol Symbol::WellKnown(napi_env env, const std::string& name) { - return Napi::Env(env).Global().Get("Symbol").As().Get(name).As(); +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { + // No need to check if the return value is a symbol or undefined. + // Well known symbols are definite and it is an develop time error + // if the symbol does not exist. +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.UnsafeAs()); + } + return Nothing(); +#else + return Napi::Env(env) + .Global() + .Get("Symbol") + .As() + .Get(name) + .UnsafeAs(); +#endif +} + +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, + std::string_view description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); } -inline Symbol::Symbol() : Name() { +inline MaybeOrValue Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif } -inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) { +inline void Symbol::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_symbol, "%d", "Symbol::CheckCast"); } +inline Symbol::Symbol() : Name() {} + +inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} + //////////////////////////////////////////////////////////////////////////////// // Automagic value creation //////////////////////////////////////////////////////////////////////////////// @@ -1014,7 +1484,7 @@ struct vf_number { } }; -template<> +template <> struct vf_number { static Boolean From(napi_env env, bool value) { return Boolean::New(env, value); @@ -1046,36 +1516,33 @@ struct vf_utf16_string { template struct vf_fallback { - static Value From(napi_env env, const T& value) { - return Value(env, value); - } + static Value From(napi_env env, const T& value) { return Value(env, value); } }; -template struct disjunction : std::false_type {}; -template struct disjunction : B {}; +template +struct disjunction : std::false_type {}; +template +struct disjunction : B {}; template struct disjunction : std::conditional>::type {}; template struct can_make_string - : disjunction::type, - typename std::is_convertible::type, + : disjunction::type, + typename std::is_convertible::type, typename std::is_convertible::type, typename std::is_convertible::type> {}; -} +} // namespace details template Value Value::From(napi_env env, const T& value) { using Helper = typename std::conditional< - std::is_integral::value || std::is_floating_point::value, - details::vf_number, - typename std::conditional< - details::can_make_string::value, - String, - details::vf_fallback - >::type - >::type; + std::is_integral::value || std::is_floating_point::value, + details::vf_number, + typename std::conditional::value, + String, + details::vf_fallback>::type>::type; return Helper::From(env, value); } @@ -1083,43 +1550,83 @@ template String String::From(napi_env env, const T& value) { struct Dummy {}; using Helper = typename std::conditional< - std::is_convertible::value, - details::vf_utf8_charp, - typename std::conditional< - std::is_convertible::value, - details::vf_utf16_charp, + std::is_convertible::value, + details::vf_utf8_charp, typename std::conditional< - std::is_convertible::value, - details::vf_utf8_string, - typename std::conditional< - std::is_convertible::value, - details::vf_utf16_string, - Dummy - >::type - >::type - >::type - >::type; + std::is_convertible::value, + details::vf_utf16_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf8_string, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_string, + Dummy>::type>::type>::type>::type; return Helper::From(env, value); } +//////////////////////////////////////////////////////////////////////////////// +// TypeTaggable class +//////////////////////////////////////////////////////////////////////////////// + +inline TypeTaggable::TypeTaggable() : Value() {} + +inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value) + : Value(_env, _value) {} + +#if NAPI_VERSION >= 8 + +inline void TypeTaggable::TypeTag(const napi_type_tag* type_tag) const { + napi_status status = napi_type_tag_object(_env, _value, type_tag); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const { + bool result; + napi_status status = + napi_check_object_type_tag(_env, _value, type_tag, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +#endif // NAPI_VERSION >= 8 + //////////////////////////////////////////////////////////////////////////////// // Object class //////////////////////////////////////////////////////////////////////////////// template inline Object::PropertyLValue::operator Value() const { - return Object(_env, _object).Get(_key); + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif } -template template -inline Object::PropertyLValue& Object::PropertyLValue::operator =(ValueType value) { - Object(_env, _object).Set(_key, value); +template +template +inline Object::PropertyLValue& Object::PropertyLValue::operator=( + ValueType value) { +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif return *this; } +template +inline Value Object::PropertyLValue::AsValue() const { + return Value(*this); +} + template inline Object::PropertyLValue::PropertyLValue(Object object, Key key) - : _env(object.Env()), _object(object), _key(key) {} + : _env(object.Env()), _object(object), _key(key) {} inline Object Object::New(napi_env env) { napi_value value; @@ -1128,229 +1635,249 @@ inline Object Object::New(napi_env env) { return Object(env, value); } -inline Object::Object() : Value() { -} +inline void Object::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); -inline Object::Object(napi_env env, napi_value value) : Value(env, value) { + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_object || type == napi_function, + "Object::CheckCast", + "Expect napi_object or napi_function, but got %d.", + type); } -inline Object::PropertyLValue Object::operator [](const char* utf8name) { +inline Object::Object() : TypeTaggable() {} + +inline Object::Object(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +inline Object::PropertyLValue Object::operator[]( + const char* utf8name) { return PropertyLValue(*this, utf8name); } -inline Object::PropertyLValue Object::operator [](const std::string& utf8name) { +inline Object::PropertyLValue Object::operator[]( + const std::string& utf8name) { return PropertyLValue(*this, utf8name); } -inline Object::PropertyLValue Object::operator [](uint32_t index) { +inline Object::PropertyLValue Object::operator[](uint32_t index) { return PropertyLValue(*this, index); } -inline Value Object::operator [](const char* utf8name) const { +inline Object::PropertyLValue Object::operator[](Value index) const { + return PropertyLValue(*this, index); +} + +inline MaybeOrValue Object::operator[](const char* utf8name) const { return Get(utf8name); } -inline Value Object::operator [](const std::string& utf8name) const { +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { return Get(utf8name); } -inline Value Object::operator [](uint32_t index) const { +inline MaybeOrValue Object::operator[](uint32_t index) const { return Get(index); } -inline bool Object::Has(napi_value key) const { +inline MaybeOrValue Object::Has(napi_value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(Value key) const { +inline MaybeOrValue Object::Has(Value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const char* utf8name) const { +inline MaybeOrValue Object::Has(const char* utf8name) const { bool result; napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const std::string& utf8name) const { +inline MaybeOrValue Object::Has(const std::string& utf8name) const { return Has(utf8name.c_str()); } -inline bool Object::HasOwnProperty(napi_value key) const { +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(Value key) const { +inline MaybeOrValue Object::HasOwnProperty(Value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(const char* utf8name) const { +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { napi_value key; - napi_status status = napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); - NAPI_THROW_IF_FAILED(_env, status, false); + napi_status status = + napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); return HasOwnProperty(key); } -inline bool Object::HasOwnProperty(const std::string& utf8name) const { +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { return HasOwnProperty(utf8name.c_str()); } -inline Value Object::Get(napi_value key) const { +inline MaybeOrValue Object::Get(napi_value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(Value key) const { +inline MaybeOrValue Object::Get(Value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const char* utf8name) const { +inline MaybeOrValue Object::Get(const char* utf8name) const { napi_value result; napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const std::string& utf8name) const { +inline MaybeOrValue Object::Get(const std::string& utf8name) const { return Get(utf8name.c_str()); } template -inline void Object::Set(napi_value key, const ValueType& value) { +inline MaybeOrValue Object::Set(napi_value key, + const ValueType& value) const { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void Object::Set(Value key, const ValueType& value) { +inline MaybeOrValue Object::Set(Value key, const ValueType& value) const { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void Object::Set(const char* utf8name, const ValueType& value) { +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) const { napi_status status = napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void Object::Set(const std::string& utf8name, const ValueType& value) { - Set(utf8name.c_str(), value); +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) const { + return Set(utf8name.c_str(), value); } -inline bool Object::Delete(napi_value key) { +inline MaybeOrValue Object::Delete(napi_value key) const { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(Value key) { +inline MaybeOrValue Object::Delete(Value key) const { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(const char* utf8name) { +inline MaybeOrValue Object::Delete(const char* utf8name) const { return Delete(String::New(_env, utf8name)); } -inline bool Object::Delete(const std::string& utf8name) { +inline MaybeOrValue Object::Delete(const std::string& utf8name) const { return Delete(String::New(_env, utf8name)); } -inline bool Object::Has(uint32_t index) const { +inline MaybeOrValue Object::Has(uint32_t index) const { bool result; napi_status status = napi_has_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Value Object::Get(uint32_t index) const { +inline MaybeOrValue Object::Get(uint32_t index) const { napi_value value; napi_status status = napi_get_element(_env, _value, index, &value); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); } template -inline void Object::Set(uint32_t index, const ValueType& value) { +inline MaybeOrValue Object::Set(uint32_t index, + const ValueType& value) const { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::Delete(uint32_t index) { +inline MaybeOrValue Object::Delete(uint32_t index) const { bool result; napi_status status = napi_delete_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Array Object::GetPropertyNames() const { +inline MaybeOrValue Object::GetPropertyNames() const { napi_value result; napi_status status = napi_get_property_names(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Array()); - return Array(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); } -inline void Object::DefineProperty(const PropertyDescriptor& property) { - napi_status status = napi_define_properties(_env, _value, 1, - reinterpret_cast(&property)); - NAPI_THROW_IF_FAILED_VOID(_env, status); +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) const { + napi_status status = napi_define_properties( + _env, + _value, + 1, + reinterpret_cast(&property)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline void Object::DefineProperties(const std::initializer_list& properties) { - napi_status status = napi_define_properties(_env, _value, properties.size(), - reinterpret_cast(properties.begin())); - NAPI_THROW_IF_FAILED_VOID(_env, status); +inline MaybeOrValue Object::DefineProperties( + const std::initializer_list& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.begin())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline void Object::DefineProperties(const std::vector& properties) { - napi_status status = napi_define_properties(_env, _value, properties.size(), - reinterpret_cast(properties.data())); - NAPI_THROW_IF_FAILED_VOID(_env, status); +inline MaybeOrValue Object::DefineProperties( + const std::vector& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.data())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::InstanceOf(const Function& constructor) const { +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } template -inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) { +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, nullptr }); + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); napi_status status = - details::AttachData(_env, - *this, - data, - details::FinalizeData::Wrapper, - finalizeData); + details::AttachData::Wrapper>( + _env, *this, data, finalizeData); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED_VOID(_env, status); @@ -1360,21 +1887,121 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) { template inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data, - Hint* finalizeHint) { + Hint* finalizeHint) const { details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, finalizeHint }); - napi_status status = - details::AttachData(_env, - *this, - data, - details::FinalizeData::WrapperWithHint, - finalizeData); + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = details:: + AttachData::WrapperWithHint>( + _env, *this, data, finalizeData); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED_VOID(_env, status); } } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +inline Object::const_iterator::const_iterator(const Object* object, + const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::const_iterator Napi::Object::begin() const { + const_iterator it(this, Object::const_iterator::Type::BEGIN); + return it; +} + +inline Object::const_iterator Napi::Object::end() const { + const_iterator it(this, Object::const_iterator::Type::END); + return it; +} + +inline Object::const_iterator& Object::const_iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::const_iterator::operator==( + const const_iterator& other) const { + return _index == other._index; +} + +inline bool Object::const_iterator::operator!=( + const const_iterator& other) const { + return _index != other._index; +} + +inline const std::pair> +Object::const_iterator::operator*() const { + const Value key = _keys[_index]; + const PropertyLValue value = (*_object)[key]; + return {key, value}; +} + +inline Object::iterator::iterator(Object* object, const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::iterator Napi::Object::begin() { + iterator it(this, Object::iterator::Type::BEGIN); + return it; +} + +inline Object::iterator Napi::Object::end() { + iterator it(this, Object::iterator::Type::END); + return it; +} + +inline Object::iterator& Object::iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::iterator::operator==(const iterator& other) const { + return _index == other._index; +} + +inline bool Object::iterator::operator!=(const iterator& other) const { + return _index != other._index; +} + +inline std::pair> +Object::iterator::operator*() { + Value key = _keys[_index]; + PropertyLValue value = (*_object)[key]; + return {key, value}; +} +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 +inline MaybeOrValue Object::Freeze() const { + napi_status status = napi_object_freeze(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Seal() const { + napi_status status = napi_object_seal(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif // NAPI_VERSION >= 8 + +inline MaybeOrValue Object::GetPrototype() const { + napi_value result; + napi_status status = napi_get_prototype(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Object(_env, result), Object); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +inline MaybeOrValue Object::SetPrototype(const Object& value) const { + napi_status status = node_api_set_prototype(_env, _value, value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// @@ -1382,7 +2009,8 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, template inline External External::New(napi_env env, T* data) { napi_value value; - napi_status status = napi_create_external(env, data, nullptr, nullptr, &value); + napi_status status = + napi_create_external(env, data, nullptr, nullptr, &value); NAPI_THROW_IF_FAILED(env, status, External()); return External(env, value); } @@ -1394,13 +2022,14 @@ inline External External::New(napi_env env, Finalizer finalizeCallback) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, nullptr }); - napi_status status = napi_create_external( - env, - data, - details::FinalizeData::Wrapper, - finalizeData, - &value); + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external(env, + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, External()); @@ -1416,13 +2045,14 @@ inline External External::New(napi_env env, Hint* finalizeHint) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, finalizeHint }); + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); napi_status status = napi_create_external( - env, - data, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); + env, + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, External()); @@ -1431,12 +2061,21 @@ inline External External::New(napi_env env, } template -inline External::External() : Value() { +inline void External::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_external, "%d", "External::CheckCast"); } template -inline External::External(napi_env env, napi_value value) : Value(env, value) { -} +inline External::External() : TypeTaggable() {} + +template +inline External::External(napi_env env, napi_value value) + : TypeTaggable(env, value) {} template inline T* External::Data() const { @@ -1464,12 +2103,19 @@ inline Array Array::New(napi_env env, size_t length) { return Array(env, value); } -inline Array::Array() : Object() { -} +inline void Array::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); -inline Array::Array(napi_env env, napi_value value) : Object(env, value) { + bool result; + napi_status status = napi_is_array(env, value, &result); + NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); + NAPI_CHECK(result, "Array::CheckCast", "value is not array"); } +inline Array::Array() : Object() {} + +inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} + inline uint32_t Array::Length() const { uint32_t result; napi_status status = napi_get_array_length(_env, _value, &result); @@ -1477,6 +2123,55 @@ inline uint32_t Array::Length() const { return result; } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +//////////////////////////////////////////////////////////////////////////////// +// SharedArrayBuffer class +//////////////////////////////////////////////////////////////////////////////// + +inline SharedArrayBuffer::SharedArrayBuffer() : Object() {} + +inline SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value) + : Object(env, value) {} + +inline void SharedArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "SharedArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = node_api_is_sharedarraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "SharedArrayBuffer::CheckCast", + "node_api_is_sharedarraybuffer failed"); + NAPI_CHECK( + result, "SharedArrayBuffer::CheckCast", "value is not sharedarraybuffer"); +} + +inline SharedArrayBuffer SharedArrayBuffer::New(napi_env env, + size_t byteLength) { + napi_value value; + void* data; + napi_status status = + node_api_create_sharedarraybuffer(env, byteLength, &data, &value); + NAPI_THROW_IF_FAILED(env, status, SharedArrayBuffer()); + + return SharedArrayBuffer(env, value); +} + +inline void* SharedArrayBuffer::Data() { + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; +} + +inline size_t SharedArrayBuffer::ByteLength() { + size_t length; + napi_status status = + napi_get_arraybuffer_info(_env, _value, nullptr, &length); + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; +} +#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + //////////////////////////////////////////////////////////////////////////////// // ArrayBuffer class //////////////////////////////////////////////////////////////////////////////// @@ -1490,12 +2185,13 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { return ArrayBuffer(env, value); } +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED inline ArrayBuffer ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength) { napi_value value; napi_status status = napi_create_external_arraybuffer( - env, externalData, byteLength, nullptr, nullptr, &value); + env, externalData, byteLength, nullptr, nullptr, &value); NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); return ArrayBuffer(env, value); @@ -1508,14 +2204,15 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, Finalizer finalizeCallback) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, nullptr }); + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); napi_status status = napi_create_external_arraybuffer( - env, - externalData, - byteLength, - details::FinalizeData::Wrapper, - finalizeData, - &value); + env, + externalData, + byteLength, + details::FinalizeData::Wrapper, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); @@ -1532,14 +2229,15 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, Hint* finalizeHint) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, finalizeHint }); + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); napi_status status = napi_create_external_arraybuffer( - env, - externalData, - byteLength, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); + env, + externalData, + byteLength, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); @@ -1547,13 +2245,23 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, return ArrayBuffer(env, value); } +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); -inline ArrayBuffer::ArrayBuffer() : Object() { + bool result; + napi_status status = napi_is_arraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "ArrayBuffer::CheckCast", + "napi_is_arraybuffer failed"); + NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); } +inline ArrayBuffer::ArrayBuffer() : Object() {} + inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) - : Object(env, value) { -} + : Object(env, value) {} inline void* ArrayBuffer::Data() { void* data; @@ -1564,7 +2272,8 @@ inline void* ArrayBuffer::Data() { inline size_t ArrayBuffer::ByteLength() { size_t length; - napi_status status = napi_get_arraybuffer_info(_env, _value, nullptr, &length); + napi_status status = + napi_get_arraybuffer_info(_env, _value, nullptr, &length); NAPI_THROW_IF_FAILED(_env, status, 0); return length; } @@ -1586,8 +2295,7 @@ inline void ArrayBuffer::Detach() { //////////////////////////////////////////////////////////////////////////////// // DataView class //////////////////////////////////////////////////////////////////////////////// -inline DataView DataView::New(napi_env env, - Napi::ArrayBuffer arrayBuffer) { +inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer) { return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); } @@ -1595,12 +2303,12 @@ inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset) { if (byteOffset > arrayBuffer.ByteLength()) { - NAPI_THROW(RangeError::New(env, - "Start offset is outside the bounds of the buffer"), - DataView()); + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); } - return New(env, arrayBuffer, byteOffset, - arrayBuffer.ByteLength() - byteOffset); + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); } inline DataView DataView::New(napi_env env, @@ -1608,52 +2316,94 @@ inline DataView DataView::New(napi_env env, size_t byteOffset, size_t byteLength) { if (byteOffset + byteLength > arrayBuffer.ByteLength()) { - NAPI_THROW(RangeError::New(env, "Invalid DataView length"), + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), DataView()); } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } napi_value value; - napi_status status = napi_create_dataview( - env, byteLength, arrayBuffer, byteOffset, &value); + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); NAPI_THROW_IF_FAILED(env, status, DataView()); return DataView(env, value); } +#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER -inline DataView::DataView() : Object() { +inline void DataView::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_dataview(env, value, &result); + NAPI_CHECK( + status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); + NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); } +inline DataView::DataView() : Object() {} + inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { - napi_status status = napi_get_dataview_info( - _env, - _value /* dataView */, - &_length /* byteLength */, - &_data /* data */, - nullptr /* arrayBuffer */, - nullptr /* byteOffset */); + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + &_length /* byteLength */, + &_data /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); NAPI_THROW_IF_FAILED_VOID(_env, status); } inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + return Buffer().As(); +} + +inline Napi::Value DataView::Buffer() const { napi_value arrayBuffer; - napi_status status = napi_get_dataview_info( - _env, - _value /* dataView */, - nullptr /* byteLength */, - nullptr /* data */, - &arrayBuffer /* arrayBuffer */, - nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); - return Napi::ArrayBuffer(_env, arrayBuffer); + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + &arrayBuffer /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); } inline size_t DataView::ByteOffset() const { size_t byteOffset; - napi_status status = napi_get_dataview_info( - _env, - _value /* dataView */, - nullptr /* byteLength */, - nullptr /* data */, - nullptr /* arrayBuffer */, - &byteOffset /* byteOffset */); + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + &byteOffset /* byteOffset */); NAPI_THROW_IF_FAILED(_env, status, 0); return byteOffset; } @@ -1734,8 +2484,9 @@ template inline T DataView::ReadData(size_t byteOffset) const { if (byteOffset + sizeof(T) > _length || byteOffset + sizeof(T) < byteOffset) { // overflow - NAPI_THROW(RangeError::New(_env, - "Offset is outside the bounds of the DataView"), 0); + NAPI_THROW( + RangeError::New(_env, "Offset is outside the bounds of the DataView"), + 0); } return *reinterpret_cast(static_cast(_data) + byteOffset); @@ -1745,8 +2496,8 @@ template inline void DataView::WriteData(size_t byteOffset, T value) const { if (byteOffset + sizeof(T) > _length || byteOffset + sizeof(T) < byteOffset) { // overflow - NAPI_THROW_VOID(RangeError::New(_env, - "Offset is outside the bounds of the DataView")); + NAPI_THROW_VOID( + RangeError::New(_env, "Offset is outside the bounds of the DataView")); } *reinterpret_cast(static_cast(_data) + byteOffset) = value; @@ -1755,35 +2506,48 @@ inline void DataView::WriteData(size_t byteOffset, T value) const { //////////////////////////////////////////////////////////////////////////////// // TypedArray class //////////////////////////////////////////////////////////////////////////////// +inline void TypedArray::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); -inline TypedArray::TypedArray() - : Object(), _type(TypedArray::unknown_array_type), _length(0) { + bool result; + napi_status status = napi_is_typedarray(env, value, &result); + NAPI_CHECK( + status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); + NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); } +inline TypedArray::TypedArray() + : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} + inline TypedArray::TypedArray(napi_env env, napi_value value) - : Object(env, value), _type(TypedArray::unknown_array_type), _length(0) { + : Object(env, value), + _type(napi_typedarray_type::napi_int8_array), + _length(0) { + if (value != nullptr) { + napi_status status = + napi_get_typedarray_info(_env, + _value, + &const_cast(this)->_type, + &const_cast(this)->_length, + nullptr, + nullptr, + nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } } inline TypedArray::TypedArray(napi_env env, napi_value value, napi_typedarray_type type, size_t length) - : Object(env, value), _type(type), _length(length) { -} + : Object(env, value), _type(type), _length(length) {} inline napi_typedarray_type TypedArray::TypedArrayType() const { - if (_type == TypedArray::unknown_array_type) { - napi_status status = napi_get_typedarray_info(_env, _value, - &const_cast(this)->_type, &const_cast(this)->_length, - nullptr, nullptr, nullptr); - NAPI_THROW_IF_FAILED(_env, status, napi_int8_array); - } - return _type; } inline uint8_t TypedArray::ElementSize() const { - switch (TypedArrayType()) { + switch (_type) { case napi_int8_array: case napi_uint8_array: case napi_uint8_clamped_array: @@ -1807,20 +2571,13 @@ inline uint8_t TypedArray::ElementSize() const { } inline size_t TypedArray::ElementLength() const { - if (_type == TypedArray::unknown_array_type) { - napi_status status = napi_get_typedarray_info(_env, _value, - &const_cast(this)->_type, &const_cast(this)->_length, - nullptr, nullptr, nullptr); - NAPI_THROW_IF_FAILED(_env, status, 0); - } - return _length; } inline size_t TypedArray::ByteOffset() const { size_t byteOffset; napi_status status = napi_get_typedarray_info( - _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); + _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); NAPI_THROW_IF_FAILED(_env, status, 0); return byteOffset; } @@ -1832,20 +2589,47 @@ inline size_t TypedArray::ByteLength() const { inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { napi_value arrayBuffer; napi_status status = napi_get_typedarray_info( - _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); return Napi::ArrayBuffer(_env, arrayBuffer); } +inline Napi::Value TypedArray::Buffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); +} + //////////////////////////////////////////////////////////////////////////////// // TypedArrayOf class //////////////////////////////////////////////////////////////////////////////// +template +inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { + TypedArray::CheckCast(env, value); + napi_typedarray_type type; + napi_status status = napi_get_typedarray_info( + env, value, &type, nullptr, nullptr, nullptr, nullptr); + NAPI_CHECK(status == napi_ok, + "TypedArrayOf::CheckCast", + "napi_is_typedarray failed"); + + NAPI_INTERNAL_CHECK( + (type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && std::is_same::value)), + "TypedArrayOf::CheckCast", + "Array type must match the template parameter, (Uint8 arrays may " + "optionally have the \"clamped\" array type.), got %d.", + type); +} template inline TypedArrayOf TypedArrayOf::New(napi_env env, size_t elementLength, napi_typedarray_type type) { - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(env, elementLength * sizeof (T)); + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, elementLength * sizeof(T)); return New(env, elementLength, arrayBuffer, 0, type); } @@ -1857,25 +2641,52 @@ inline TypedArrayOf TypedArrayOf::New(napi_env env, napi_typedarray_type type) { napi_value value; napi_status status = napi_create_typedarray( - env, type, elementLength, arrayBuffer, bufferOffset, &value); + env, type, elementLength, arrayBuffer, bufferOffset, &value); NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); return TypedArrayOf( - env, value, type, elementLength, - reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + bufferOffset)); + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER template -inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) { +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); } +#endif + +template +inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} template inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) - : TypedArray(env, value), _data(nullptr) { + : TypedArray(env, value), _data(nullptr) { napi_status status = napi_ok; if (value != nullptr) { + void* data = nullptr; status = napi_get_typedarray_info( - _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); + _env, _value, &_type, &_length, &data, nullptr, nullptr); + _data = static_cast(data); } else { _type = TypedArrayTypeForPrimitiveType(); _length = 0; @@ -1889,21 +2700,24 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, napi_typedarray_type type, size_t length, T* data) - : TypedArray(env, value, type, length), _data(data) { + : TypedArray(env, value, type, length), _data(data) { if (!(type == TypedArrayTypeForPrimitiveType() || - (type == napi_uint8_clamped_array && std::is_same::value))) { - NAPI_THROW_VOID(TypeError::New(env, "Array type must match the template parameter. " - "(Uint8 arrays may optionally have the \"clamped\" array type.)")); + (type == napi_uint8_clamped_array && + std::is_same::value))) { + NAPI_THROW_VOID(TypeError::New( + env, + "Array type must match the template parameter. " + "(Uint8 arrays may optionally have the \"clamped\" array type.)")); } } template -inline T& TypedArrayOf::operator [](size_t index) { +inline T& TypedArrayOf::operator[](size_t index) { return _data[index]; } template -inline const T& TypedArrayOf::operator [](size_t index) const { +inline const T& TypedArrayOf::operator[](size_t index) const { return _data[index]; } @@ -1922,12 +2736,11 @@ inline const T* TypedArrayOf::Data() const { //////////////////////////////////////////////////////////////////////////////// template -static inline napi_status -CreateFunction(napi_env env, - const char* utf8name, - napi_callback cb, - CbData* data, - napi_value* result) { +inline napi_status CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { napi_status status = napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); if (status == napi_ok) { @@ -1982,16 +2795,13 @@ inline Function Function::New(napi_env env, Callable cb, const char* utf8name, void* data) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; - auto callbackData = new CbData({ cb, data }); + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + auto callbackData = new CbData{std::move(cb), data}; napi_value value; - napi_status status = CreateFunction(env, - utf8name, - CbData::Wrapper, - callbackData, - &value); + napi_status status = + CreateFunction(env, utf8name, CbData::Wrapper, callbackData, &value); if (status != napi_ok) { delete callbackData; NAPI_THROW_IF_FAILED(env, status, Function()); @@ -2008,84 +2818,128 @@ inline Function Function::New(napi_env env, return New(env, cb, utf8name.c_str(), data); } -inline Function::Function() : Object() { +inline void Function::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_function, "%d", "Function::CheckCast"); } -inline Function::Function(napi_env env, napi_value value) : Object(env, value) { +inline Function::Function() : Object() {} + +inline Function::Function(napi_env env, napi_value value) + : Object(env, value) {} + +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); } -inline Value Function::operator ()(const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + const std::vector& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::vector& args) const { +inline MaybeOrValue Function::Call( + const std::vector& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { return Call(Env().Undefined(), argc, args); } -inline Value Function::Call(napi_value recv, const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { return Call(recv, args.size(), args.begin()); } -inline Value Function::Call(napi_value recv, const std::vector& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { return Call(recv, args.size(), args.data()); } -inline Value Function::Call(napi_value recv, size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + const size_t argc = args.size(); + const size_t stackArgsCount = 6; + napi_value stackArgs[stackArgsCount]; + std::vector heapArgs; + napi_value* argv; + if (argc <= stackArgsCount) { + argv = stackArgs; + } else { + heapArgs.resize(argc); + argv = heapArgs.data(); + } + + for (size_t index = 0; index < argc; index++) { + argv[index] = static_cast(args[index]); + } + + return Call(recv, argc, argv); +} + +inline MaybeOrValue Function::Call(napi_value recv, + size_t argc, + const napi_value* args) const { napi_value result; - napi_status status = napi_call_function( - _env, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + napi_status status = + napi_call_function(_env, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.begin(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.data(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, size_t argc, const napi_value* args, napi_async_context context) const { napi_value result; - napi_status status = napi_make_callback( - _env, context, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + napi_status status = + napi_make_callback(_env, context, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Object Function::New(const std::initializer_list& args) const { +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { return New(args.size(), args.begin()); } -inline Object Function::New(const std::vector& args) const { +inline MaybeOrValue Function::New( + const std::vector& args) const { return New(args.size(), args.data()); } -inline Object Function::New(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { napi_value result; - napi_status status = napi_new_instance( - _env, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + napi_status status = napi_new_instance(_env, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -2119,7 +2973,102 @@ inline void Promise::Deferred::Reject(napi_value value) const { NAPI_THROW_IF_FAILED_VOID(_env, status); } -inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) { +inline void Promise::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_promise(env, value, &result); + NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); + NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); +} + +inline Promise::Promise() : Object() {} + +inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = thenMethod.Call(*this, {onFulfilled}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled, + napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled, onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = + thenMethod.Call(*this, {onFulfilled, onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Catch(napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value catchMethod; + if (!Get("catch").UnwrapTo(&catchMethod)) { + return Nothing(); + } + MaybeOrValue result = + catchMethod.As().Call(*this, {onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function catchMethod = Get("catch").As(); + MaybeOrValue result = catchMethod.Call(*this, {onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled) const { + return Then(static_cast(onFulfilled)); +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled, + const Function& onRejected) const { + return Then(static_cast(onFulfilled), + static_cast(onRejected)); +} + +inline MaybeOrValue Promise::Catch(const Function& onRejected) const { + return Catch(static_cast(onRejected)); } //////////////////////////////////////////////////////////////////////////////// @@ -2130,18 +3079,20 @@ template inline Buffer Buffer::New(napi_env env, size_t length) { napi_value value; void* data; - napi_status status = napi_create_buffer(env, length * sizeof (T), &data, &value); + napi_status status = + napi_create_buffer(env, length * sizeof(T), &data, &value); NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value, length, static_cast(data)); + return Buffer(env, value); } +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED template inline Buffer Buffer::New(napi_env env, T* data, size_t length) { napi_value value; napi_status status = napi_create_external_buffer( - env, length * sizeof (T), data, nullptr, nullptr, &value); + env, length * sizeof(T), data, nullptr, nullptr, &value); NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value, length, data); + return Buffer(env, value); } template @@ -2152,19 +3103,20 @@ inline Buffer Buffer::New(napi_env env, Finalizer finalizeCallback) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, nullptr }); - napi_status status = napi_create_external_buffer( - env, - length * sizeof (T), - data, - details::FinalizeData::Wrapper, - finalizeData, - &value); + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, Buffer()); } - return Buffer(env, value, length, data); + return Buffer(env, value); } template @@ -2176,69 +3128,144 @@ inline Buffer Buffer::New(napi_env env, Hint* finalizeHint) { napi_value value; details::FinalizeData* finalizeData = - new details::FinalizeData({ finalizeCallback, finalizeHint }); + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); napi_status status = napi_create_external_buffer( - env, - length * sizeof (T), - data, - details::FinalizeData::WrapperWithHint, - finalizeData, - &value); + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, Buffer()); } - return Buffer(env, value, length, data); + return Buffer(env, value); } +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED template -inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { +inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED napi_value value; - napi_status status = napi_create_buffer_copy( - env, length * sizeof (T), data, nullptr, &value); + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + return Buffer::Copy(env, data, length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } NAPI_THROW_IF_FAILED(env, status, Buffer()); - return Buffer(env, value); + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED } template -inline Buffer::Buffer() : Uint8Array(), _length(0), _data(nullptr) { +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGC(env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED } template -inline Buffer::Buffer(napi_env env, napi_value value) - : Uint8Array(env, value), _length(0), _data(nullptr) { +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGCWithHint( + env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif } template -inline Buffer::Buffer(napi_env env, napi_value value, size_t length, T* data) - : Uint8Array(env, value), _length(length), _data(data) { +inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { + napi_value value; + napi_status status = + napi_create_buffer_copy(env, length * sizeof(T), data, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); } template -inline size_t Buffer::Length() const { - EnsureInfo(); - return _length; +inline void Buffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_buffer(env, value, &result); + NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); + NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); } template -inline T* Buffer::Data() const { - EnsureInfo(); - return _data; +inline Buffer::Buffer() : Uint8Array() {} + +template +inline Buffer::Buffer(napi_env env, napi_value value) + : Uint8Array(env, value) {} + +template +inline size_t Buffer::Length() const { + return ByteLength() / sizeof(T); } template -inline void Buffer::EnsureInfo() const { - // The Buffer instance may have been constructed from a napi_value whose - // length/data are not yet known. Fetch and cache these values just once, - // since they can never change during the lifetime of the Buffer. - if (_data == nullptr) { - size_t byteLength; - void* voidData; - napi_status status = napi_get_buffer_info(_env, _value, &voidData, &byteLength); - NAPI_THROW_IF_FAILED_VOID(_env, status); - _length = byteLength / sizeof (T); - _data = static_cast(voidData); - } +inline T* Buffer::Data() const { + return reinterpret_cast(const_cast(Uint8Array::Data())); } //////////////////////////////////////////////////////////////////////////////// @@ -2249,12 +3276,23 @@ inline Error Error::New(napi_env env) { napi_status status; napi_value error = nullptr; bool is_exception_pending; - const napi_extended_error_info* info; + napi_extended_error_info last_error_info_copy; - // We must retrieve the last error info before doing anything else, because - // doing anything else will replace the last error info. - status = napi_get_last_error_info(env, &info); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); + { + // We must retrieve the last error info before doing anything else because + // doing anything else will replace the last error info. + const napi_extended_error_info* last_error_info; + status = napi_get_last_error_info(env, &last_error_info); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); + + // All fields of the `napi_extended_error_info` structure gets reset in + // subsequent Node-API function calls on the same `env`. This includes a + // call to `napi_is_exception_pending()`. So here it is necessary to make a + // copy of the information as the `error_code` field is used later on. + memcpy(&last_error_info_copy, + last_error_info, + sizeof(napi_extended_error_info)); + } status = napi_is_exception_pending(env, &is_exception_pending); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); @@ -2262,30 +3300,28 @@ inline Error Error::New(napi_env env) { // A pending exception takes precedence over any internal error status. if (is_exception_pending) { status = napi_get_and_clear_last_exception(env, &error); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); - } - else { - const char* error_message = info->error_message != nullptr ? - info->error_message : "Error in native callback"; + NAPI_FATAL_IF_FAILED( + status, "Error::New", "napi_get_and_clear_last_exception"); + } else { + const char* error_message = last_error_info_copy.error_message != nullptr + ? last_error_info_copy.error_message + : "Error in native callback"; napi_value message; status = napi_create_string_utf8( - env, - error_message, - std::strlen(error_message), - &message); + env, error_message, std::strlen(error_message), &message); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); - switch (info->error_code) { - case napi_object_expected: - case napi_string_expected: - case napi_boolean_expected: - case napi_number_expected: - status = napi_create_type_error(env, nullptr, message, &error); - break; - default: - status = napi_create_error(env, nullptr, message, &error); - break; + switch (last_error_info_copy.error_code) { + case napi_object_expected: + case napi_string_expected: + case napi_boolean_expected: + case napi_number_expected: + status = napi_create_type_error(env, nullptr, message, &error); + break; + default: + status = napi_create_error(env, nullptr, message, &error); + break; } NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); } @@ -2294,42 +3330,127 @@ inline Error Error::New(napi_env env) { } inline Error Error::New(napi_env env, const char* message) { - return Error::New(env, message, std::strlen(message), napi_create_error); + return Error::New( + env, message, std::strlen(message), napi_create_error); } inline Error Error::New(napi_env env, const std::string& message) { - return Error::New(env, message.c_str(), message.size(), napi_create_error); + return Error::New( + env, message.c_str(), message.size(), napi_create_error); } -inline NAPI_NO_RETURN void Error::Fatal(const char* location, const char* message) { +inline NAPI_NO_RETURN void Error::Fatal(const char* location, + const char* message) { napi_fatal_error(location, NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH); } -inline Error::Error() : ObjectReference() { -} +inline Error::Error() : ObjectReference() {} -inline Error::Error(napi_env env, napi_value value) : ObjectReference(env, nullptr) { +inline Error::Error(napi_env env, napi_value value) + : ObjectReference(env, nullptr) { if (value != nullptr) { + // Attempting to create a reference on the error object. + // If it's not a Object/Function/Symbol, this call will return an error + // status. napi_status status = napi_create_reference(env, value, 1, &_ref); + if (status != napi_ok) { + napi_value wrappedErrorObj; + + // Create an error object + status = napi_create_object(env, &wrappedErrorObj); + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); + + // property flag that we attach to show the error object is wrapped + napi_property_descriptor wrapObjFlag = { + ERROR_WRAP_VALUE(), // Unique GUID identifier since Symbol isn't a + // viable option + nullptr, + nullptr, + nullptr, + nullptr, + Value::From(env, value), + napi_enumerable, + nullptr}; + + status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + if (status == napi_pending_exception) { + // Test if the pending exception was reported because the environment is + // shutting down. We assume that a status of napi_pending_exception + // coupled with the absence of an actual pending exception means that + // the environment is shutting down. If so, we replace the + // napi_pending_exception status with napi_ok. + bool is_exception_pending = false; + status = napi_is_exception_pending(env, &is_exception_pending); + if (status == napi_ok && !is_exception_pending) { + status = napi_ok; + } else { + status = napi_pending_exception; + } + } +#endif // NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); + + // Create a reference on the newly wrapped object + status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); + } + // Avoid infinite recursion in the failure case. - // Don't try to construct & throw another Error instance. NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); } } -inline Error::Error(Error&& other) : ObjectReference(std::move(other)) { +inline Object Error::Value() const { + if (_ref == nullptr) { + return Object(_env, nullptr); + } + + napi_value refValue; + napi_status status = napi_get_reference_value(_env, _ref, &refValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + napi_valuetype type; + status = napi_typeof(_env, refValue, &type); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + // If refValue isn't a symbol, then we proceed to whether the refValue has the + // wrapped error flag + if (type != napi_symbol) { + // We are checking if the object is wrapped + bool isWrappedObject = false; + + status = napi_has_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &isWrappedObject); + + // Don't care about status + if (isWrappedObject) { + napi_value unwrappedValue; + status = napi_get_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &unwrappedValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + return Object(_env, unwrappedValue); + } + } + + return Object(_env, refValue); } -inline Error& Error::operator =(Error&& other) { +inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {} + +inline Error& Error::operator=(Error&& other) { static_cast*>(this)->operator=(std::move(other)); return *this; } -inline Error::Error(const Error& other) : ObjectReference(other) { -} +inline Error::Error(const Error& other) : ObjectReference(other) {} -inline Error& Error::operator =(const Error& other) { +inline Error& Error::operator=(const Error& other) { Reset(); _env = other.Env(); @@ -2346,48 +3467,91 @@ inline Error& Error::operator =(const Error& other) { inline const std::string& Error::Message() const NAPI_NOEXCEPT { if (_message.size() == 0 && _env != nullptr) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { _message = Get("message").As(); - } - catch (...) { + } catch (...) { // Catch all errors here, to include e.g. a std::bad_alloc from // the std::string::operator=, because this method may not throw. } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else _message = Get("message").As(); -#endif // NAPI_CPP_EXCEPTIONS +#endif +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } return _message; } +// we created an object on the &_ref inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + bool pendingException = false; + // check if there is already a pending exception. If so don't try to throw a + // new one as that is not allowed/possible + napi_status status = napi_is_exception_pending(_env, &pendingException); + + if ((status != napi_ok) || + ((status == napi_ok) && (pendingException == false))) { + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + status = napi_throw(_env, Value()); + +#if (NAPI_VERSION >= 10) + napi_status expected_failure_mode = napi_cannot_run_js; +#else + napi_status expected_failure_mode = napi_pending_exception; +#endif + if (status == expected_failure_mode) { + // The environment must be terminating as we checked earlier and there + // was no pending exception. In this case continuing will result + // in a fatal error and there is nothing the author has done incorrectly + // in their code that is worth flagging through a fatal error + return; + } + } else { + status = napi_pending_exception; + } +#else // We intentionally don't use `NAPI_THROW_*` macros here to ensure // that there is no possible recursion as `ThrowAsJavaScriptException` // is part of `NAPI_THROW_*` macro definition for noexcept. napi_status status = napi_throw(_env, Value()); +#endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS if (status != napi_ok) { throw Error::New(_env); } -#else // NAPI_CPP_EXCEPTIONS - NAPI_FATAL_IF_FAILED(status, "Error::ThrowAsJavaScriptException", "napi_throw"); -#endif // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS + NAPI_FATAL_IF_FAILED( + status, "Error::ThrowAsJavaScriptException", "napi_throw"); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } } -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS inline const char* Error::what() const NAPI_NOEXCEPT { return Message().c_str(); } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { + return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; +} template inline TError Error::New(napi_env env, @@ -2406,39 +3570,59 @@ inline TError Error::New(napi_env env, } inline TypeError TypeError::New(napi_env env, const char* message) { - return Error::New(env, message, std::strlen(message), napi_create_type_error); + return Error::New( + env, message, std::strlen(message), napi_create_type_error); } inline TypeError TypeError::New(napi_env env, const std::string& message) { - return Error::New(env, message.c_str(), message.size(), napi_create_type_error); + return Error::New( + env, message.c_str(), message.size(), napi_create_type_error); } -inline TypeError::TypeError() : Error() { -} +inline TypeError::TypeError() : Error() {} -inline TypeError::TypeError(napi_env env, napi_value value) : Error(env, value) { -} +inline TypeError::TypeError(napi_env env, napi_value value) + : Error(env, value) {} inline RangeError RangeError::New(napi_env env, const char* message) { - return Error::New(env, message, std::strlen(message), napi_create_range_error); + return Error::New( + env, message, std::strlen(message), napi_create_range_error); } inline RangeError RangeError::New(napi_env env, const std::string& message) { - return Error::New(env, message.c_str(), message.size(), napi_create_range_error); + return Error::New( + env, message.c_str(), message.size(), napi_create_range_error); } -inline RangeError::RangeError() : Error() { +inline RangeError::RangeError() : Error() {} + +inline RangeError::RangeError(napi_env env, napi_value value) + : Error(env, value) {} + +#if NAPI_VERSION > 8 +inline SyntaxError SyntaxError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), node_api_create_syntax_error); } -inline RangeError::RangeError(napi_env env, napi_value value) : Error(env, value) { +inline SyntaxError SyntaxError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), node_api_create_syntax_error); } +inline SyntaxError::SyntaxError() : Error() {} + +inline SyntaxError::SyntaxError(napi_env env, napi_value value) + : Error(env, value) {} +#endif // NAPI_VERSION > 8 + //////////////////////////////////////////////////////////////////////////////// // Reference class //////////////////////////////////////////////////////////////////////////////// template -inline Reference Reference::New(const T& value, uint32_t initialRefcount) { +inline Reference Reference::New(const T& value, + uint32_t initialRefcount) { napi_env env = value.Env(); napi_value val = value; @@ -2453,21 +3637,27 @@ inline Reference Reference::New(const T& value, uint32_t initialRefcount) return Reference(env, ref); } - template -inline Reference::Reference() : _env(nullptr), _ref(nullptr), _suppressDestruct(false) { -} +inline Reference::Reference() + : _env(nullptr), _ref(nullptr), _suppressDestruct(false) {} template inline Reference::Reference(napi_env env, napi_ref ref) - : _env(env), _ref(ref), _suppressDestruct(false) { -} + : _env(env), _ref(ref), _suppressDestruct(false) {} template inline Reference::~Reference() { if (_ref != nullptr) { if (!_suppressDestruct) { + // TODO(legendecas): napi_delete_reference should be invoked immediately. + // Fix this when https://github.com/nodejs/node/pull/55620 lands. +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + Env().PostFinalizer( + [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); }, + _ref); +#else napi_delete_reference(_env, _ref); +#endif } _ref = nullptr; @@ -2476,14 +3666,16 @@ inline Reference::~Reference() { template inline Reference::Reference(Reference&& other) - : _env(other._env), _ref(other._ref), _suppressDestruct(other._suppressDestruct) { + : _env(other._env), + _ref(other._ref), + _suppressDestruct(other._suppressDestruct) { other._env = nullptr; other._ref = nullptr; other._suppressDestruct = false; } template -inline Reference& Reference::operator =(Reference&& other) { +inline Reference& Reference::operator=(Reference&& other) { Reset(); _env = other._env; _ref = other._ref; @@ -2496,15 +3688,17 @@ inline Reference& Reference::operator =(Reference&& other) { template inline Reference::Reference(const Reference& other) - : _env(other._env), _ref(nullptr), _suppressDestruct(false) { + : _env(other._env), _ref(nullptr), _suppressDestruct(false) { HandleScope scope(_env); napi_value value = other.Value(); if (value != nullptr) { - // Copying is a limited scenario (currently only used for Error object) and always creates a - // strong reference to the given value even if the incoming reference is weak. + // Copying is a limited scenario (currently only used for Error object) and + // always creates a strong reference to the given value even if the incoming + // reference is weak. napi_status status = napi_create_reference(_env, value, 1, &_ref); - NAPI_FATAL_IF_FAILED(status, "Reference::Reference", "napi_create_reference"); + NAPI_FATAL_IF_FAILED( + status, "Reference::Reference", "napi_create_reference"); } } @@ -2514,14 +3708,14 @@ inline Reference::operator napi_ref() const { } template -inline bool Reference::operator ==(const Reference &other) const { +inline bool Reference::operator==(const Reference& other) const { HandleScope scope(_env); return this->Value().StrictEquals(other.Value()); } template -inline bool Reference::operator !=(const Reference &other) const { - return !this->operator ==(other); +inline bool Reference::operator!=(const Reference& other) const { + return !this->operator==(other); } template @@ -2547,18 +3741,18 @@ inline T Reference::Value() const { } template -inline uint32_t Reference::Ref() { +inline uint32_t Reference::Ref() const { uint32_t result; napi_status status = napi_reference_ref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 1); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } template -inline uint32_t Reference::Unref() { +inline uint32_t Reference::Unref() const { uint32_t result; napi_status status = napi_reference_unref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 1); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -2618,257 +3812,397 @@ inline FunctionReference Persistent(Function value) { // ObjectReference class //////////////////////////////////////////////////////////////////////////////// -inline ObjectReference::ObjectReference(): Reference() { -} +inline ObjectReference::ObjectReference() : Reference() {} -inline ObjectReference::ObjectReference(napi_env env, napi_ref ref): Reference(env, ref) { -} +inline ObjectReference::ObjectReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} inline ObjectReference::ObjectReference(Reference&& other) - : Reference(std::move(other)) { -} + : Reference(std::move(other)) {} -inline ObjectReference& ObjectReference::operator =(Reference&& other) { +inline ObjectReference& ObjectReference::operator=(Reference&& other) { static_cast*>(this)->operator=(std::move(other)); return *this; } inline ObjectReference::ObjectReference(ObjectReference&& other) - : Reference(std::move(other)) { -} + : Reference(std::move(other)) {} -inline ObjectReference& ObjectReference::operator =(ObjectReference&& other) { +inline ObjectReference& ObjectReference::operator=(ObjectReference&& other) { static_cast*>(this)->operator=(std::move(other)); return *this; } inline ObjectReference::ObjectReference(const ObjectReference& other) - : Reference(other) { -} + : Reference(other) {} -inline Napi::Value ObjectReference::Get(const char* utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value ObjectReference::Get(const std::string& utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline void ObjectReference::Set(const char* utf8name, napi_value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) const { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const char* utf8name, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) const { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const char* utf8name, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) const { HandleScope scope(_env); - Value().Set(utf8name, utf8value); + return Value().Set(utf8name, utf8value); } -inline void ObjectReference::Set(const char* utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) const { HandleScope scope(_env); - Value().Set(utf8name, boolValue); + return Value().Set(utf8name, boolValue); } -inline void ObjectReference::Set(const char* utf8name, double numberValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) const { HandleScope scope(_env); - Value().Set(utf8name, numberValue); + return Value().Set(utf8name, numberValue); } -inline void ObjectReference::Set(const std::string& utf8name, napi_value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) const { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const std::string& utf8name, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) const { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const std::string& utf8name, std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set( + const std::string& utf8name, const std::string& utf8value) const { HandleScope scope(_env); - Value().Set(utf8name, utf8value); + return Value().Set(utf8name, utf8value); } -inline void ObjectReference::Set(const std::string& utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) const { HandleScope scope(_env); - Value().Set(utf8name, boolValue); + return Value().Set(utf8name, boolValue); } -inline void ObjectReference::Set(const std::string& utf8name, double numberValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) const { HandleScope scope(_env); - Value().Set(utf8name, numberValue); + return Value().Set(utf8name, numberValue); } -inline Napi::Value ObjectReference::Get(uint32_t index) const { +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(index)); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline void ObjectReference::Set(uint32_t index, napi_value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) const { HandleScope scope(_env); - Value().Set(index, value); + return Value().Set(index, value); } -inline void ObjectReference::Set(uint32_t index, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) const { HandleScope scope(_env); - Value().Set(index, value); + return Value().Set(index, value); } -inline void ObjectReference::Set(uint32_t index, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) const { HandleScope scope(_env); - Value().Set(index, utf8value); + return Value().Set(index, utf8value); } -inline void ObjectReference::Set(uint32_t index, const std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set( + uint32_t index, const std::string& utf8value) const { HandleScope scope(_env); - Value().Set(index, utf8value); + return Value().Set(index, utf8value); } -inline void ObjectReference::Set(uint32_t index, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + bool boolValue) const { HandleScope scope(_env); - Value().Set(index, boolValue); + return Value().Set(index, boolValue); } -inline void ObjectReference::Set(uint32_t index, double numberValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) const { HandleScope scope(_env); - Value().Set(index, numberValue); + return Value().Set(index, numberValue); } //////////////////////////////////////////////////////////////////////////////// // FunctionReference class //////////////////////////////////////////////////////////////////////////////// -inline FunctionReference::FunctionReference(): Reference() { -} +inline FunctionReference::FunctionReference() : Reference() {} inline FunctionReference::FunctionReference(napi_env env, napi_ref ref) - : Reference(env, ref) { -} + : Reference(env, ref) {} inline FunctionReference::FunctionReference(Reference&& other) - : Reference(std::move(other)) { -} + : Reference(std::move(other)) {} -inline FunctionReference& FunctionReference::operator =(Reference&& other) { +inline FunctionReference& FunctionReference::operator=( + Reference&& other) { static_cast*>(this)->operator=(std::move(other)); return *this; } inline FunctionReference::FunctionReference(FunctionReference&& other) - : Reference(std::move(other)) { -} + : Reference(std::move(other)) {} -inline FunctionReference& FunctionReference::operator =(FunctionReference&& other) { +inline FunctionReference& FunctionReference::operator=( + FunctionReference&& other) { static_cast*>(this)->operator=(std::move(other)); return *this; } -inline Napi::Value FunctionReference::operator ()( +inline MaybeOrValue FunctionReference::operator()( const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value()(args)); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::vector& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, size_t argc, const napi_value* args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, argc, args); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, size_t argc, const napi_value* args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, argc, args, context); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Object FunctionReference::New(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } -inline Object FunctionReference::New(const std::vector& args) const { +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -2876,10 +4210,15 @@ inline Object FunctionReference::New(const std::vector& args) const //////////////////////////////////////////////////////////////////////////////// inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) - : _env(env), _info(info), _this(nullptr), _dynamicArgs(nullptr), _data(nullptr) { + : _env(env), + _info(info), + _this(nullptr), + _dynamicArgs(nullptr), + _data(nullptr) { _argc = _staticArgCount; _argv = _staticArgs; - napi_status status = napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); + napi_status status = + napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); NAPI_THROW_IF_FAILED_VOID(_env, status); if (_argc > _staticArgCount) { @@ -2899,6 +4238,10 @@ inline CallbackInfo::~CallbackInfo() { } } +inline CallbackInfo::operator napi_callback_info() const { + return _info; +} + inline Value CallbackInfo::NewTarget() const { napi_value newTarget; napi_status status = napi_get_new_target(_env, _info, &newTarget); @@ -2918,7 +4261,7 @@ inline size_t CallbackInfo::Length() const { return _argc; } -inline const Value CallbackInfo::operator [](size_t index) const { +inline const Value CallbackInfo::operator[](size_t index) const { return index < _argc ? Value(_env, _argv[index]) : Env().Undefined(); } @@ -2942,10 +4285,8 @@ inline void CallbackInfo::SetData(void* data) { //////////////////////////////////////////////////////////////////////////////// template -PropertyDescriptor -PropertyDescriptor::Accessor(const char* utf8name, - napi_property_attributes attributes, - void* data) { +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; @@ -2957,18 +4298,16 @@ PropertyDescriptor::Accessor(const char* utf8name, } template -PropertyDescriptor -PropertyDescriptor::Accessor(const std::string& utf8name, - napi_property_attributes attributes, - void* data) { +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), attributes, data); } template -PropertyDescriptor -PropertyDescriptor::Accessor(Name name, - napi_property_attributes attributes, - void* data) { +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; @@ -2979,14 +4318,10 @@ PropertyDescriptor::Accessor(Name name, return desc; } -template < -typename PropertyDescriptor::GetterCallback Getter, -typename PropertyDescriptor::SetterCallback Setter> -PropertyDescriptor -PropertyDescriptor::Accessor(const char* utf8name, - napi_property_attributes attributes, - void* data) { - +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; @@ -2998,23 +4333,19 @@ PropertyDescriptor::Accessor(const char* utf8name, return desc; } -template < -typename PropertyDescriptor::GetterCallback Getter, -typename PropertyDescriptor::SetterCallback Setter> -PropertyDescriptor -PropertyDescriptor::Accessor(const std::string& utf8name, - napi_property_attributes attributes, - void* data) { +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), attributes, data); } -template < -typename PropertyDescriptor::GetterCallback Getter, -typename PropertyDescriptor::SetterCallback Setter> -PropertyDescriptor -PropertyDescriptor::Accessor(Name name, - napi_property_attributes attributes, - void* data) { +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; @@ -3027,15 +4358,15 @@ PropertyDescriptor::Accessor(Name name, } template -inline PropertyDescriptor -PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { - typedef details::CallbackData CbData; - auto callbackData = new CbData({ getter, data }); +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); napi_status status = AttachData(env, object, callbackData); if (status != napi_ok) { @@ -3043,37 +4374,37 @@ PropertyDescriptor::Accessor(Napi::Env env, NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); } - return PropertyDescriptor({ - utf8name, - nullptr, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData - }); + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { return Accessor(env, object, utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { - typedef details::CallbackData CbData; - auto callbackData = new CbData({ getter, data }); +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); napi_status status = AttachData(env, object, callbackData); if (status != napi_ok) { @@ -3081,28 +4412,27 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); } - return PropertyDescriptor({ - nullptr, - name, - nullptr, - CbData::Wrapper, - nullptr, - nullptr, - attributes, - callbackData - }); + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - typedef details::AccessorCallbackData CbData; - auto callbackData = new CbData({ getter, setter, data }); +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); napi_status status = AttachData(env, object, callbackData); if (status != napi_ok) { @@ -3110,39 +4440,40 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); } - return PropertyDescriptor({ - utf8name, - nullptr, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData - }); + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - return Accessor(env, object, utf8name.c_str(), getter, setter, attributes, data); +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor( + env, object, utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - typedef details::AccessorCallbackData CbData; - auto callbackData = new CbData({ getter, setter, data }); +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); napi_status status = AttachData(env, object, callbackData); if (status != napi_ok) { @@ -3150,99 +4481,99 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); } - return PropertyDescriptor({ - nullptr, - name, - nullptr, - CbData::GetterWrapper, - CbData::SetterWrapper, - nullptr, - attributes, - callbackData - }); + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); } template -inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, - Napi::Object /*object*/, - const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return PropertyDescriptor({ - utf8name, - nullptr, - nullptr, - nullptr, - nullptr, - Napi::Function::New(env, cb, utf8name, data), - attributes, - nullptr - }); +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, utf8name, data), + attributes, + nullptr}); } template -inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { return Function(env, object, utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, - Napi::Object /*object*/, - Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { - return PropertyDescriptor({ - nullptr, - name, - nullptr, - nullptr, - nullptr, - Napi::Function::New(env, cb, nullptr, data), - attributes, - nullptr - }); -} - -inline PropertyDescriptor PropertyDescriptor::Value(const char* utf8name, - napi_value value, - napi_property_attributes attributes) { - return PropertyDescriptor({ - utf8name, nullptr, nullptr, nullptr, nullptr, value, attributes, nullptr - }); +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({nullptr, + name, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, nullptr, data), + attributes, + nullptr}); } -inline PropertyDescriptor PropertyDescriptor::Value(const std::string& utf8name, - napi_value value, - napi_property_attributes attributes) { +inline PropertyDescriptor PropertyDescriptor::Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + value, + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes) { return Value(utf8name.c_str(), value, attributes); } -inline PropertyDescriptor PropertyDescriptor::Value(napi_value name, - napi_value value, - napi_property_attributes attributes) { - return PropertyDescriptor({ - nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr - }); +inline PropertyDescriptor PropertyDescriptor::Value( + napi_value name, napi_value value, napi_property_attributes attributes) { + return PropertyDescriptor( + {nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr}); } -inline PropertyDescriptor PropertyDescriptor::Value(Name name, - Napi::Value value, - napi_property_attributes attributes) { +inline PropertyDescriptor PropertyDescriptor::Value( + Name name, Napi::Value value, napi_property_attributes attributes) { napi_value nameValue = name; napi_value valueValue = value; return PropertyDescriptor::Value(nameValue, valueValue, attributes); } inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) - : _desc(desc) { -} + : _desc(desc) {} inline PropertyDescriptor::operator napi_property_descriptor&() { return _desc; @@ -3257,26 +4588,22 @@ inline PropertyDescriptor::operator const napi_property_descriptor&() const { //////////////////////////////////////////////////////////////////////////////// template -inline void InstanceWrap::AttachPropData(napi_env env, - napi_value value, - const napi_property_descriptor* prop) { +inline void InstanceWrap::AttachPropData( + napi_env env, napi_value value, const napi_property_descriptor* prop) { napi_status status; - if (prop->method != nullptr && !(prop->attributes & napi_static)) { + if (!(prop->attributes & napi_static)) { if (prop->method == T::InstanceVoidMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); NAPI_THROW_IF_FAILED_VOID(env, status); } else if (prop->method == T::InstanceMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); NAPI_THROW_IF_FAILED_VOID(env, status); } else if (prop->getter == T::InstanceGetterCallbackWrapper || - prop->setter == T::InstanceSetterCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); NAPI_THROW_IF_FAILED_VOID(env, status); } } @@ -3289,7 +4616,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( napi_property_attributes attributes, void* data) { InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); + new InstanceVoidMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; @@ -3305,7 +4632,8 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; @@ -3322,7 +4650,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( napi_property_attributes attributes, void* data) { InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); + new InstanceVoidMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; @@ -3338,7 +4666,8 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; @@ -3351,57 +4680,72 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( template template ::InstanceVoidMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // MSVC (as of v145 / Visual Studio 2026) raises an internal compiler error + // (C1001) when a pointer-to-member-function is used as a non-type template + // parameter, as the static compile-time dispatch below does. On MSVC, fall + // back to the runtime overload, which passes `method` as a value instead. + return InstanceMethod(utf8name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(utf8name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceVoidMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceMethodCallback method> inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceMethod(name, method, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; +#endif } template @@ -3412,7 +4756,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( napi_property_attributes attributes, void* data) { InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + new InstanceAccessorCallbackData({getter, setter, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; @@ -3431,7 +4775,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( napi_property_attributes attributes, void* data) { InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + new InstanceAccessorCallbackData({getter, setter, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; @@ -3446,9 +4790,11 @@ template template ::InstanceGetterCallback getter, typename InstanceWrap::InstanceSetterCallback setter> inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceAccessor(utf8name, getter, setter, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.getter = details::TemplatedInstanceCallback; @@ -3456,15 +4802,18 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( desc.data = data; desc.attributes = attributes; return desc; +#endif } template template ::InstanceGetterCallback getter, typename InstanceWrap::InstanceSetterCallback setter> inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { +#ifdef _MSC_VER + // See the note in the InstanceMethod overload above. + return InstanceAccessor(name, getter, setter, attributes, data); +#else napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.getter = details::TemplatedInstanceCallback; @@ -3472,6 +4821,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( desc.data = data; desc.attributes = attributes; return desc; +#endif } template @@ -3488,9 +4838,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceValue( template inline ClassPropertyDescriptor InstanceWrap::InstanceValue( - Symbol name, - Napi::Value value, - napi_property_attributes attributes) { + Symbol name, Napi::Value value, napi_property_attributes attributes) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.value = value; @@ -3500,73 +4848,70 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceValue( template inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); T* instance = T::Unwrap(callbackInfo.This().As()); auto cb = callbackData->callback; - (instance->*cb)(callbackInfo); + if (instance) (instance->*cb)(callbackInfo); return nullptr; }); } template inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); T* instance = T::Unwrap(callbackInfo.This().As()); auto cb = callbackData->callback; - return (instance->*cb)(callbackInfo); + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); }); } template inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); T* instance = T::Unwrap(callbackInfo.This().As()); auto cb = callbackData->getterCallback; - return (instance->*cb)(callbackInfo); + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); }); } template inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); T* instance = T::Unwrap(callbackInfo.This().As()); auto cb = callbackData->setterCallback; - (instance->*cb)(callbackInfo, callbackInfo[0]); + if (instance) (instance->*cb)(callbackInfo, callbackInfo[0]); return nullptr; }); } template template ::InstanceSetterCallback method> -inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { +inline napi_value InstanceWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo, cbInfo[0]); + if (instance) (instance->*method)(cbInfo, cbInfo[0]); return nullptr; }); } @@ -3576,7 +4921,8 @@ inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_inf //////////////////////////////////////////////////////////////////////////////// template -inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { +inline NAPI_NO_SANITIZE_VPTR ObjectWrap::ObjectWrap( + const Napi::CallbackInfo& callbackInfo) { napi_env env = callbackInfo.Env(); napi_value wrapper = callbackInfo.This(); napi_status status; @@ -3590,10 +4936,10 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { } template -inline ObjectWrap::~ObjectWrap() { +inline NAPI_NO_SANITIZE_VPTR ObjectWrap::~ObjectWrap() { // If the JS object still exists at this point, remove the finalizer added // through `napi_wrap()`. - if (!IsEmpty()) { + if (!IsEmpty() && !_finalized) { Object object = Value(); // It is not valid to call `napi_remove_wrap()` with an empty `object`. // This happens e.g. during garbage collection. @@ -3603,21 +4949,25 @@ inline ObjectWrap::~ObjectWrap() { } } -template -inline T* ObjectWrap::Unwrap(Object wrapper) { - T* unwrapped; - napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); +// with RTTI turned on, modern compilers check to see if virtual function +// pointers are stripped of RTTI by void casts. this is intrinsic to how Unwrap +// works, so we inject a compiler pragma to turn off that check just for the +// affected methods. this compiler check is on by default in Android NDK 29. +template +inline NAPI_NO_SANITIZE_VPTR T* ObjectWrap::Unwrap(Object wrapper) { + void* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); - return unwrapped; + return static_cast(unwrapped); } template -inline Function -ObjectWrap::DefineClass(Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* descriptors, - void* data) { +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { napi_status status; std::vector props(props_count); @@ -3633,16 +4983,18 @@ ObjectWrap::DefineClass(Napi::Env env, props[index] = descriptors[index]; napi_property_descriptor* prop = &props[index]; if (prop->method == T::StaticMethodCallbackWrapper) { - status = CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); NAPI_THROW_IF_FAILED(env, status, Function()); prop->method = nullptr; prop->data = nullptr; } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { - status = CreateFunction(env, + status = + CreateFunction(env, utf8name, prop->method, static_cast(prop->data), @@ -3672,9 +5024,8 @@ ObjectWrap::DefineClass(Napi::Env env, if (prop->getter == T::StaticGetterCallbackWrapper || prop->setter == T::StaticSetterCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); NAPI_THROW_IF_FAILED(env, status, Function()); } else { // InstanceWrap::AttachPropData is responsible for attaching the data @@ -3692,11 +5043,12 @@ inline Function ObjectWrap::DefineClass( const char* utf8name, const std::initializer_list>& properties, void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.begin()), - data); + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); } template @@ -3705,11 +5057,12 @@ inline Function ObjectWrap::DefineClass( const char* utf8name, const std::vector>& properties, void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.data()), - data); + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); } template @@ -3718,13 +5071,15 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3734,13 +5089,15 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = T::StaticMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3750,13 +5107,15 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3766,69 +5125,67 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = T::StaticMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template template ::StaticVoidMethodCallback method> inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedVoidCallback; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template template ::StaticVoidMethodCallback method> inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedVoidCallback; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template template ::StaticMethodCallback method> inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.method = details::TemplatedCallback; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template template ::StaticMethodCallback method> inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.method = details::TemplatedCallback; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3840,14 +5197,15 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( napi_property_attributes attributes, void* data) { StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + new StaticAccessorCallbackData({getter, setter, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3859,14 +5217,15 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( napi_property_attributes attributes, void* data) { StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + new StaticAccessorCallbackData({getter, setter, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3874,15 +5233,14 @@ template template ::StaticGetterCallback getter, typename ObjectWrap::StaticSetterCallback setter> inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - const char* utf8name, - napi_property_attributes attributes, - void* data) { + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.getter = details::TemplatedCallback; desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } @@ -3890,59 +5248,73 @@ template template ::StaticGetterCallback getter, typename ObjectWrap::StaticSetterCallback setter> inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( - Symbol name, - napi_property_attributes attributes, - void* data) { + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.getter = details::TemplatedCallback; desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8name, - Napi::Value value, napi_property_attributes attributes) { +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; desc.value = value; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticValue(Symbol name, - Napi::Value value, napi_property_attributes attributes) { +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; desc.value = value; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = + static_cast(attributes | napi_static); return desc; } +template +inline Value ObjectWrap::OnCalledAsFunction( + const Napi::CallbackInfo& callbackInfo) { + NAPI_THROW( + TypeError::New(callbackInfo.Env(), + "Class constructors cannot be invoked without 'new'"), + Napi::Value()); +} + template inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} +template +inline void ObjectWrap::Finalize(BasicEnv /*env*/) {} + template inline napi_value ObjectWrap::ConstructorCallbackWrapper( - napi_env env, - napi_callback_info info) { + napi_env env, napi_callback_info info) { napi_value new_target; napi_status status = napi_get_new_target(env, info, &new_target); if (status != napi_ok) return nullptr; bool isConstructCall = (new_target != nullptr); if (!isConstructCall) { - napi_throw_type_error(env, nullptr, "Class constructors cannot be invoked without 'new'"); - return nullptr; + return details::WrapCallback( + env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); } - napi_value wrapper = details::WrapCallback([&] { + napi_value wrapper = details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); T* instance = new T(callbackInfo); -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS instance->_construction_failed = false; #else if (callbackInfo.Env().IsExceptionPending()) { @@ -3953,7 +5325,7 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( } else { instance->_construction_failed = false; } -# endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS return callbackInfo.This(); }); @@ -3962,12 +5334,11 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( template inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->callback(callbackInfo); return nullptr; @@ -3976,12 +5347,11 @@ inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->callback(callbackInfo); }); @@ -3989,12 +5359,11 @@ inline napi_value ObjectWrap::StaticMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticGetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->getterCallback(callbackInfo); }); @@ -4002,12 +5371,11 @@ inline napi_value ObjectWrap::StaticGetterCallbackWrapper( template inline napi_value ObjectWrap::StaticSetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); + reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->setterCallback(callbackInfo, callbackInfo[0]); return nullptr; @@ -4015,8 +5383,61 @@ inline napi_value ObjectWrap::StaticSetterCallbackWrapper( } template -inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { - HandleScope scope(env); +inline void ObjectWrap::FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* /*hint*/) { + // If the child class does not override _any_ Finalize() method, `env` will be + // unused because of the constexpr guards. Explicitly reference it here to + // bypass compiler warnings. + (void)env; + T* instance = static_cast(data); + + // Prevent ~ObjectWrap from calling napi_remove_wrap. + // The instance->_ref should be deleted with napi_delete_reference in + // ~Reference. + instance->_finalized = true; + + // If class overrides the basic finalizer, execute it. + if constexpr (details::HasBasicFinalizer::value) { +#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + HandleScope scope(env); +#endif + + instance->Finalize(Napi::BasicEnv(env)); + } + + // If class overrides the (extended) finalizer, either schedule it or + // execute it immediately (depending on experimental features enabled). + if constexpr (details::HasExtendedFinalizer::value) { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + // In experimental, attach via node_api_post_finalizer. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + napi_status status = + node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr); + NAPI_FATAL_IF_FAILED(status, + "ObjectWrap::FinalizeCallback", + "node_api_post_finalizer failed"); +#else + // In non-experimental, this `FinalizeCallback` already executes from a + // non-basic environment. Execute the override directly. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + HandleScope scope(env); + PostFinalizeCallback(env, data, static_cast(nullptr)); +#endif + } + // If the instance does _not_ override the (extended) finalizer, delete the + // `T* instance` immediately. + else { + delete instance; + } +} + +template +inline void ObjectWrap::PostFinalizeCallback(napi_env env, + void* data, + void* /*hint*/) { T* instance = static_cast(data); instance->Finalize(Napi::Env(env)); delete instance; @@ -4024,10 +5445,14 @@ inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hi template template ::StaticSetterCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { +inline napi_value ObjectWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); - method(cbInfo, cbInfo[0]); + // MSVC requires to copy 'method' function pointer to a local variable + // before invoking it. + auto m = method; + m(cbInfo, cbInfo[0]); return nullptr; }); } @@ -4037,8 +5462,7 @@ inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info //////////////////////////////////////////////////////////////////////////////// inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) - : _env(env), _scope(scope) { -} + : _env(env), _scope(scope) {} inline HandleScope::HandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_handle_scope(_env, &_scope); @@ -4047,9 +5471,8 @@ inline HandleScope::HandleScope(Napi::Env env) : _env(env) { inline HandleScope::~HandleScope() { napi_status status = napi_close_handle_scope(_env, _scope); - NAPI_FATAL_IF_FAILED(status, - "HandleScope::~HandleScope", - "napi_close_handle_scope"); + NAPI_FATAL_IF_FAILED( + status, "HandleScope::~HandleScope", "napi_close_handle_scope"); } inline HandleScope::operator napi_handle_scope() const { @@ -4065,8 +5488,8 @@ inline Napi::Env HandleScope::Env() const { //////////////////////////////////////////////////////////////////////////////// inline EscapableHandleScope::EscapableHandleScope( - napi_env env, napi_escapable_handle_scope scope) : _env(env), _scope(scope) { -} + napi_env env, napi_escapable_handle_scope scope) + : _env(env), _scope(scope) {} inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_escapable_handle_scope(_env, &_scope); @@ -4095,28 +5518,25 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { return Value(_env, result); } - #if (NAPI_VERSION > 2) //////////////////////////////////////////////////////////////////////////////// // CallbackScope class //////////////////////////////////////////////////////////////////////////////// -inline CallbackScope::CallbackScope( - napi_env env, napi_callback_scope scope) : _env(env), _scope(scope) { -} +inline CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope) + : _env(env), _scope(scope) {} inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) : _env(env) { - napi_status status = napi_open_callback_scope( - _env, Object::New(env), context, &_scope); + napi_status status = + napi_open_callback_scope(_env, Object::New(env), context, &_scope); NAPI_THROW_IF_FAILED_VOID(_env, status); } inline CallbackScope::~CallbackScope() { napi_status status = napi_close_callback_scope(_env, _scope); - NAPI_FATAL_IF_FAILED(status, - "CallbackScope::~CallbackScope", - "napi_close_callback_scope"); + NAPI_FATAL_IF_FAILED( + status, "CallbackScope::~CallbackScope", "napi_close_callback_scope"); } inline CallbackScope::operator napi_callback_scope() const { @@ -4133,14 +5553,12 @@ inline Napi::Env CallbackScope::Env() const { //////////////////////////////////////////////////////////////////////////////// inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) - : AsyncContext(env, resource_name, Object::New(env)) { -} + : AsyncContext(env, resource_name, Object::New(env)) {} inline AsyncContext::AsyncContext(napi_env env, - const char* resource_name, + const char* resource_name, const Object& resource) - : _env(env), - _context(nullptr) { + : _env(env), _context(nullptr) { napi_value resource_id; napi_status status = napi_create_string_utf8( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); @@ -4164,7 +5582,7 @@ inline AsyncContext::AsyncContext(AsyncContext&& other) { other._context = nullptr; } -inline AsyncContext& AsyncContext::operator =(AsyncContext&& other) { +inline AsyncContext& AsyncContext::operator=(AsyncContext&& other) { _env = other._env; other._env = nullptr; _context = other._context; @@ -4184,79 +5602,75 @@ inline Napi::Env AsyncContext::Env() const { // AsyncWorker class //////////////////////////////////////////////////////////////////////////////// +#if NAPI_HAS_THREADS + inline AsyncWorker::AsyncWorker(const Function& callback) - : AsyncWorker(callback, "generic") { -} + : AsyncWorker(callback, "generic") {} inline AsyncWorker::AsyncWorker(const Function& callback, const char* resource_name) - : AsyncWorker(callback, resource_name, Object::New(callback.Env())) { -} + : AsyncWorker(callback, resource_name, Object::New(callback.Env())) {} inline AsyncWorker::AsyncWorker(const Function& callback, const char* resource_name, const Object& resource) - : AsyncWorker(Object::New(callback.Env()), - callback, - resource_name, - resource) { -} + : AsyncWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} inline AsyncWorker::AsyncWorker(const Object& receiver, const Function& callback) - : AsyncWorker(receiver, callback, "generic") { -} + : AsyncWorker(receiver, callback, "generic") {} inline AsyncWorker::AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name) - : AsyncWorker(receiver, - callback, - resource_name, - Object::New(callback.Env())) { -} + : AsyncWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} inline AsyncWorker::AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource) - : _env(callback.Env()), - _receiver(Napi::Persistent(receiver)), - _callback(Napi::Persistent(callback)), - _suppress_destruct(false) { + : _env(callback.Env()), + _receiver(Napi::Persistent(receiver)), + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { napi_value resource_id; napi_status status = napi_create_string_latin1( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); NAPI_THROW_IF_FAILED_VOID(_env, status); - status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, - OnAsyncWorkComplete, this, &_work); + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); NAPI_THROW_IF_FAILED_VOID(_env, status); } -inline AsyncWorker::AsyncWorker(Napi::Env env) - : AsyncWorker(env, "generic") { -} +inline AsyncWorker::AsyncWorker(Napi::Env env) : AsyncWorker(env, "generic") {} -inline AsyncWorker::AsyncWorker(Napi::Env env, - const char* resource_name) - : AsyncWorker(env, resource_name, Object::New(env)) { -} +inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) {} inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name, const Object& resource) - : _env(env), - _receiver(), - _callback(), - _suppress_destruct(false) { + : _env(env), _receiver(), _callback(), _suppress_destruct(false) { napi_value resource_id; napi_status status = napi_create_string_latin1( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); NAPI_THROW_IF_FAILED_VOID(_env, status); - status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, - OnAsyncWorkComplete, this, &_work); + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); NAPI_THROW_IF_FAILED_VOID(_env, status); } @@ -4271,29 +5685,6 @@ inline void AsyncWorker::Destroy() { delete this; } -inline AsyncWorker::AsyncWorker(AsyncWorker&& other) { - _env = other._env; - other._env = nullptr; - _work = other._work; - other._work = nullptr; - _receiver = std::move(other._receiver); - _callback = std::move(other._callback); - _error = std::move(other._error); - _suppress_destruct = other._suppress_destruct; -} - -inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) { - _env = other._env; - other._env = nullptr; - _work = other._work; - other._work = nullptr; - _receiver = std::move(other._receiver); - _callback = std::move(other._callback); - _error = std::move(other._error); - _suppress_destruct = other._suppress_destruct; - return *this; -} - inline AsyncWorker::operator napi_async_work() const { return _work; } @@ -4332,7 +5723,8 @@ inline void AsyncWorker::OnOK() { inline void AsyncWorker::OnError(const Error& e) { if (!_callback.IsEmpty()) { - _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); + _callback.Call(_receiver.Value(), + std::initializer_list{e.Value()}); } } @@ -4356,15 +5748,15 @@ inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { // must not run any method that would cause JavaScript to run. In practice, // this means that almost any use of napi_env will be incorrect. inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { Execute(); } catch (const std::exception& e) { SetError(e.what()); } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS Execute(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, @@ -4373,14 +5765,13 @@ inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, AsyncWorker* self = static_cast(asyncworker); self->OnWorkComplete(env, status); } -inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { +inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { if (status != napi_cancelled) { HandleScope scope(_env); - details::WrapCallback([&] { + details::WrapCallback(env, [&] { if (_error.size() == 0) { OnOK(); - } - else { + } else { OnError(Error::New(_env, _error)); } return nullptr; @@ -4391,7 +5782,9 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { } } -#if (NAPI_VERSION > 3 && !defined(__wasm32__)) +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) //////////////////////////////////////////////////////////////////////////////// // TypedThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// @@ -4489,19 +5882,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - nullptr, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -4533,19 +5928,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - nullptr, - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -4649,19 +6046,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - callback, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -4695,6 +6094,9 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; napi_status status = napi_create_threadsafe_function( env, details::DefaultCallbackWrapper< @@ -4706,8 +6108,7 @@ TypedThreadSafeFunction::New( maxQueueSize, initialThreadCount, finalizeData, - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, + fini, context, CallJsInternal, &tsfn._tsfn); @@ -4794,7 +6195,7 @@ template inline napi_status -TypedThreadSafeFunction::Release() { +TypedThreadSafeFunction::Release() const { return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); } @@ -4802,7 +6203,7 @@ template inline napi_status -TypedThreadSafeFunction::Abort() { +TypedThreadSafeFunction::Abort() const { return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); } @@ -4883,68 +6284,93 @@ TypedThreadSafeFunction::FunctionOrEmpty( // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount); + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New( + env, callback, Object(), resourceName, maxQueueSize, initialThreadCount); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount, context); + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount, finalizeCallback); + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback); } // static -template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount, finalizeCallback, data); + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback, + data); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount, context, finalizeCallback); + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback); } // static -template +template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, ResourceString resourceName, @@ -4953,89 +6379,128 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data) { - return New(env, callback, Object(), resourceName, maxQueueSize, - initialThreadCount, context, finalizeCallback, data); + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, static_cast(nullptr) /* context */); + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, context, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, [](Env, ContextType*) {} /* empty finalizer */); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, static_cast(nullptr) /* context */, - finalizeCallback, static_cast(nullptr) /* data */, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + static_cast(nullptr) /* data */, details::ThreadSafeFinalize::Wrapper); } // static -template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, static_cast(nullptr) /* context */, - finalizeCallback, data, - details::ThreadSafeFinalize< - void, Finalizer, FinalizerDataType>::FinalizeWrapperWithData); + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeWrapperWithData); } // static template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, context, finalizeCallback, - static_cast(nullptr) /* data */, - details::ThreadSafeFinalize< - ContextType, Finalizer>::FinalizeWrapperWithContext); + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::FinalizeWrapperWithContext); } // static -template +template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -5045,20 +6510,24 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, context, finalizeCallback, data, - details::ThreadSafeFinalize::FinalizeFinalizeWrapperWithDataAndContext); + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext); } -inline ThreadSafeFunction::ThreadSafeFunction() - : _tsfn() { -} +inline ThreadSafeFunction::ThreadSafeFunction() : _tsfn() {} -inline ThreadSafeFunction::ThreadSafeFunction( - napi_threadsafe_function tsfn) - : _tsfn(tsfn) { -} +inline ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} inline ThreadSafeFunction::operator napi_threadsafe_function() const { return _tsfn; @@ -5069,20 +6538,18 @@ inline napi_status ThreadSafeFunction::BlockingCall() const { } template <> -inline napi_status ThreadSafeFunction::BlockingCall( - void* data) const { +inline napi_status ThreadSafeFunction::BlockingCall(void* data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); } template -inline napi_status ThreadSafeFunction::BlockingCall( - Callback callback) const { +inline napi_status ThreadSafeFunction::BlockingCall(Callback callback) const { return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); } template -inline napi_status ThreadSafeFunction::BlockingCall( - DataType* data, Callback callback) const { +inline napi_status ThreadSafeFunction::BlockingCall(DataType* data, + Callback callback) const { auto wrapper = [data, callback](Env env, Function jsCallback) { callback(env, jsCallback, data); }; @@ -5094,8 +6561,7 @@ inline napi_status ThreadSafeFunction::NonBlockingCall() const { } template <> -inline napi_status ThreadSafeFunction::NonBlockingCall( - void* data) const { +inline napi_status ThreadSafeFunction::NonBlockingCall(void* data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); } @@ -5132,25 +6598,29 @@ inline napi_status ThreadSafeFunction::Acquire() const { return napi_acquire_threadsafe_function(_tsfn); } -inline napi_status ThreadSafeFunction::Release() { +inline napi_status ThreadSafeFunction::Release() const { return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); } -inline napi_status ThreadSafeFunction::Abort() { +inline napi_status ThreadSafeFunction::Abort() const { return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); } -inline ThreadSafeFunction::ConvertibleContext -ThreadSafeFunction::GetContext() const { +inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() + const { void* context; napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); - NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunction::GetContext", "napi_get_threadsafe_function_context"); - return ConvertibleContext({ context }); + NAPI_FATAL_IF_FAILED(status, + "ThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return ConvertibleContext({context}); } // static -template +template inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -5161,16 +6631,26 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, Finalizer finalizeCallback, FinalizerDataType* data, napi_finalize wrapper) { - static_assert(details::can_make_string::value - || std::is_convertible::value, - "Resource name should be convertible to the string type"); + static_assert(details::can_make_string::value || + std::is_convertible::value, + "Resource name should be convertible to the string type"); ThreadSafeFunction tsfn; - auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback }); - napi_status status = napi_create_threadsafe_function(env, callback, resource, - Value::From(env, resourceName), maxQueueSize, initialThreadCount, - finalizeData, wrapper, context, CallJS, &tsfn._tsfn); + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + Value::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + wrapper, + context, + CallJS, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); @@ -5182,8 +6662,8 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, inline napi_status ThreadSafeFunction::CallInternal( CallbackWrapper* callbackWrapper, napi_threadsafe_function_call_mode mode) const { - napi_status status = napi_call_threadsafe_function( - _tsfn, callbackWrapper, mode); + napi_status status = + napi_call_threadsafe_function(_tsfn, callbackWrapper, mode); if (status != napi_ok && callbackWrapper != nullptr) { delete callbackWrapper; } @@ -5200,26 +6680,30 @@ inline void ThreadSafeFunction::CallJS(napi_env env, return; } - if (data != nullptr) { - auto* callbackWrapper = static_cast(data); - (*callbackWrapper)(env, Function(env, jsCallback)); - delete callbackWrapper; - } else if (jsCallback != nullptr) { - Function(env, jsCallback).Call({}); - } + details::WrapVoidCallback(env, [&]() { + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } + }); } //////////////////////////////////////////////////////////////////////////////// // Async Progress Worker Base class //////////////////////////////////////////////////////////////////////////////// template -inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource, - size_t queue_size) - : AsyncWorker(receiver, callback, resource_name, resource) { - // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. _tsfn = ThreadSafeFunction::New(callback.Env(), callback, resource, @@ -5233,15 +6717,18 @@ inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(const Object& #if NAPI_VERSION > 4 template -inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(Napi::Env env, - const char* resource_name, - const Object& resource, - size_t queue_size) - : AsyncWorker(env, resource_name, resource) { +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { // TODO: Once the changes to make the callback optional for threadsafe - // functions are available on all versions we can remove the dummy Function here. + // functions are available on all versions we can remove the dummy Function + // here. Function callback; - // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. _tsfn = ThreadSafeFunction::New(env, callback, resource, @@ -5254,38 +6741,45 @@ inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(Napi::Env env, } #endif -template +template inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { // Abort pending tsfn call. // Don't send progress events after we've already completed. - // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release duplicated. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release + // duplicated. _tsfn.Abort(); } template -inline void AsyncProgressWorkerBase::OnAsyncWorkProgress(Napi::Env /* env */, - Napi::Function /* jsCallback */, - void* data) { +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress( + Napi::Env /* env */, Napi::Function /* jsCallback */, void* data) { ThreadSafeData* tsd = static_cast(data); tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); delete tsd; } template -inline napi_status AsyncProgressWorkerBase::NonBlockingCall(DataType* data) { +inline napi_status AsyncProgressWorkerBase::NonBlockingCall( + DataType* data) { auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); - return _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); + auto ret = _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); + if (ret != napi_ok) { + delete tsd; + } + return ret; } template -inline void AsyncProgressWorkerBase::OnWorkComplete(Napi::Env /* env */, napi_status status) { +inline void AsyncProgressWorkerBase::OnWorkComplete( + Napi::Env /* env */, napi_status status) { _work_completed = true; _complete_status = status; _tsfn.Release(); } template -inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize(Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize( + Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { if (context->_work_completed) { context->AsyncWorker::OnWorkComplete(env, context->_complete_status); } @@ -5294,76 +6788,65 @@ inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize(Napi //////////////////////////////////////////////////////////////////////////////// // Async Progress Worker class //////////////////////////////////////////////////////////////////////////////// -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) - : AsyncProgressWorker(callback, "generic") { -} + : AsyncProgressWorker(callback, "generic") {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, - const char* resource_name) - : AsyncProgressWorker(callback, resource_name, Object::New(callback.Env())) { -} + const char* resource_name) + : AsyncProgressWorker( + callback, resource_name, Object::New(callback.Env())) {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressWorker(Object::New(callback.Env()), - callback, - resource_name, - resource) { -} + const char* resource_name, + const Object& resource) + : AsyncProgressWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, const Function& callback) - : AsyncProgressWorker(receiver, callback, "generic") { -} + : AsyncProgressWorker(receiver, callback, "generic") {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, const Function& callback, const char* resource_name) - : AsyncProgressWorker(receiver, - callback, - resource_name, - Object::New(callback.Env())) { -} + : AsyncProgressWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource) - : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), - _asyncdata(nullptr), - _asyncsize(0) { -} + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0), + _signaled(false) {} #if NAPI_VERSION > 4 -template +template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) - : AsyncProgressWorker(env, "generic") { -} + : AsyncProgressWorker(env, "generic") {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, const char* resource_name) - : AsyncProgressWorker(env, resource_name, Object::New(env)) { -} + : AsyncProgressWorker(env, resource_name, Object::New(env)) {} -template +template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Object& resource) - : AsyncProgressWorkerBase(env, resource_name, resource), - _asyncdata(nullptr), - _asyncsize(0) { -} + : AsyncProgressWorkerBase(env, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) {} #endif -template +template inline AsyncProgressWorker::~AsyncProgressWorker() { { std::lock_guard lock(this->_mutex); @@ -5372,22 +6855,25 @@ inline AsyncProgressWorker::~AsyncProgressWorker() { } } -template +template inline void AsyncProgressWorker::Execute() { ExecutionProgress progress(this); Execute(progress); } -template +template inline void AsyncProgressWorker::OnWorkProgress(void*) { T* data; size_t size; + bool signaled; { std::lock_guard lock(this->_mutex); data = this->_asyncdata; size = this->_asyncsize; + signaled = this->_signaled; this->_asyncdata = nullptr; this->_asyncsize = 0; + this->_signaled = false; } /** @@ -5397,7 +6883,7 @@ inline void AsyncProgressWorker::OnWorkProgress(void*) { * the deferring the signal of uv_async_t is been sent again, i.e. potential * not coalesced two calls of the TSFN callback. */ - if (data == nullptr) { + if (data == nullptr && !signaled) { return; } @@ -5405,119 +6891,119 @@ inline void AsyncProgressWorker::OnWorkProgress(void*) { delete[] data; } -template +template inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { - T* new_data = new T[count]; - std::copy(data, data + count, new_data); - - T* old_data; - { - std::lock_guard lock(this->_mutex); - old_data = _asyncdata; - _asyncdata = new_data; - _asyncsize = count; - } - this->NonBlockingCall(nullptr); + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(this->_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + _signaled = false; + } + this->NonBlockingCall(nullptr); - delete[] old_data; + delete[] old_data; } -template -inline void AsyncProgressWorker::Signal() const { +template +inline void AsyncProgressWorker::Signal() { + { + std::lock_guard lock(this->_mutex); + _signaled = true; + } this->NonBlockingCall(static_cast(nullptr)); } -template +template inline void AsyncProgressWorker::ExecutionProgress::Signal() const { - _worker->Signal(); + this->_worker->Signal(); } -template -inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { +template +inline void AsyncProgressWorker::ExecutionProgress::Send( + const T* data, size_t count) const { _worker->SendProgress_(data, count); } //////////////////////////////////////////////////////////////////////////////// // Async Progress Queue Worker class //////////////////////////////////////////////////////////////////////////////// -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback) - : AsyncProgressQueueWorker(callback, "generic") { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, - const char* resource_name) - : AsyncProgressQueueWorker(callback, resource_name, Object::New(callback.Env())) { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressQueueWorker(Object::New(callback.Env()), - callback, - resource_name, - resource) { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, - const Function& callback) - : AsyncProgressQueueWorker(receiver, callback, "generic") { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name) - : AsyncProgressQueueWorker(receiver, - callback, - resource_name, - Object::New(callback.Env())) { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource) - : AsyncProgressWorkerBase>(receiver, callback, resource_name, resource, /** unlimited queue size */0) { -} +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name, const Object& resource) + : AsyncProgressQueueWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>( + receiver, + callback, + resource_name, + resource, + /** unlimited queue size */ 0) {} #if NAPI_VERSION > 4 -template +template inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) - : AsyncProgressQueueWorker(env, "generic") { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, - const char* resource_name) - : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) { -} - -template -inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, - const char* resource_name, - const Object& resource) - : AsyncProgressWorkerBase>(env, resource_name, resource, /** unlimited queue size */0) { -} + : AsyncProgressQueueWorker(env, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name, const Object& resource) + : AsyncProgressWorkerBase>( + env, resource_name, resource, /** unlimited queue size */ 0) {} #endif -template +template inline void AsyncProgressQueueWorker::Execute() { ExecutionProgress progress(this); Execute(progress); } -template -inline void AsyncProgressQueueWorker::OnWorkProgress(std::pair* datapair) { +template +inline void AsyncProgressQueueWorker::OnWorkProgress( + std::pair* datapair) { if (datapair == nullptr) { return; } - T *data = datapair->first; + T* data = datapair->first; size_t size = datapair->second; this->OnProgress(data, size); @@ -5525,45 +7011,52 @@ inline void AsyncProgressQueueWorker::OnWorkProgress(std::pair* d delete[] data; } -template -inline void AsyncProgressQueueWorker::SendProgress_(const T* data, size_t count) { - T* new_data = new T[count]; - std::copy(data, data + count, new_data); +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, + size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); - auto pair = new std::pair(new_data, count); - this->NonBlockingCall(pair); + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); } -template +template inline void AsyncProgressQueueWorker::Signal() const { - this->NonBlockingCall(nullptr); + this->SendProgress_(static_cast(nullptr), 0); } -template -inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, napi_status status) { +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, + napi_status status) { // Draining queued items in TSFN. AsyncProgressWorkerBase>::OnWorkComplete(env, status); } -template +template inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { - _worker->Signal(); + _worker->SendProgress_(static_cast(nullptr), 0); } -template -inline void AsyncProgressQueueWorker::ExecutionProgress::Send(const T* data, size_t count) const { +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send( + const T* data, size_t count) const { _worker->SendProgress_(data, count); } -#endif // NAPI_VERSION > 3 && !defined(__wasm32__) +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS //////////////////////////////////////////////////////////////////////////////// // Memory Management class //////////////////////////////////////////////////////////////////////////////// -inline int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in_bytes) { +inline int64_t MemoryManagement::AdjustExternalMemory(BasicEnv env, + int64_t change_in_bytes) { int64_t result; - napi_status status = napi_adjust_external_memory(env, change_in_bytes, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + napi_status status = + napi_adjust_external_memory(env, change_in_bytes, &result); + NAPI_FATAL_IF_FAILED(status, + "MemoryManagement::AdjustExternalMemory", + "napi_adjust_external_memory"); return result; } @@ -5571,17 +7064,20 @@ inline int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in // Version Management class //////////////////////////////////////////////////////////////////////////////// -inline uint32_t VersionManagement::GetNapiVersion(Env env) { +inline uint32_t VersionManagement::GetNapiVersion(BasicEnv env) { uint32_t result; napi_status status = napi_get_version(env, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNapiVersion", "napi_get_version"); return result; } -inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { +inline const napi_node_version* VersionManagement::GetNodeVersion( + BasicEnv env) { const napi_node_version* result; napi_status status = napi_get_node_version(env, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNodeVersion", "napi_get_node_version"); return result; } @@ -5603,24 +7099,20 @@ inline T* Addon::Unwrap(Object wrapper) { } template -inline void -Addon::DefineAddon(Object exports, - const std::initializer_list& props) { +inline void Addon::DefineAddon( + Object exports, const std::initializer_list& props) { DefineProperties(exports, props); entry_point_ = exports; } template -inline Napi::Object -Addon::DefineProperties(Object object, - const std::initializer_list& props) { +inline Napi::Object Addon::DefineProperties( + Object object, const std::initializer_list& props) { const napi_property_descriptor* properties = - reinterpret_cast(props.begin()); + reinterpret_cast(props.begin()); size_t size = props.size(); - napi_status status = napi_define_properties(object.Env(), - object, - size, - properties); + napi_status status = + napi_define_properties(object.Env(), object, size, properties); NAPI_THROW_IF_FAILED(object.Env(), status, object); for (size_t idx = 0; idx < size; idx++) T::AttachPropData(object.Env(), object, &properties[idx]); @@ -5628,6 +7120,125 @@ Addon::DefineProperties(Object object, } #endif // NAPI_VERSION > 5 -} // namespace Napi +#if NAPI_VERSION > 2 +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook, Arg* arg) { + return CleanupHook(*this, hook, arg); +} + +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook) { + return CleanupHook(*this, hook); +} + +template +Env::CleanupHook::CleanupHook() { + data = nullptr; +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, Hook hook) + : wrapper(Env::CleanupHook::Wrapper) { + data = new CleanupData{std::move(hook), nullptr}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, + Hook hook, + Arg* arg) + : wrapper(Env::CleanupHook::WrapperWithArg) { + data = new CleanupData{std::move(hook), arg}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +bool Env::CleanupHook::Remove(BasicEnv env) { + napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); + delete data; + data = nullptr; + return status == napi_ok; +} + +template +bool Env::CleanupHook::IsEmpty() const { + return data == nullptr; +} +#endif // NAPI_VERSION > 2 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { + using T = void*; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithoutData, + static_cast(nullptr), + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGC, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithHint, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +#undef NAPI_NO_SANITIZE_VPTR -#endif // SRC_NAPI_INL_H_ +#endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index cf4410bd9..870a5c290 100644 --- a/napi.h +++ b/napi.h @@ -1,46 +1,87 @@ #ifndef SRC_NAPI_H_ #define SRC_NAPI_H_ +#ifndef NAPI_HAS_THREADS +#if !defined(__wasm__) || (defined(__EMSCRIPTEN_PTHREADS__) || \ + (defined(__wasi__) && defined(_REENTRANT))) +#define NAPI_HAS_THREADS 1 +#else +#define NAPI_HAS_THREADS 0 +#endif +#endif + #include #include #include #include +#if NAPI_HAS_THREADS #include +#endif // NAPI_HAS_THREADS +#include #include +#include #include -// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known good version) +// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known +// good version) #if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 #define NAPI_HAS_CONSTEXPR 1 #endif -// VS2013 does not support char16_t literal strings, so we'll work around it using wchar_t strings -// and casting them. This is safe as long as the character sizes are the same. +// VS2013 does not support char16_t literal strings, so we'll work around it +// using wchar_t strings and casting them. This is safe as long as the character +// sizes are the same. #if defined(_MSC_VER) && _MSC_VER <= 1800 -static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16_t and wchar_t"); -#define NAPI_WIDE_TEXT(x) reinterpret_cast(L ## x) +static_assert(sizeof(char16_t) == sizeof(wchar_t), + "Size mismatch between char16_t and wchar_t"); +#define NAPI_WIDE_TEXT(x) reinterpret_cast(L##x) #else -#define NAPI_WIDE_TEXT(x) u ## x +#define NAPI_WIDE_TEXT(x) u##x +#endif + +// Backwards-compatibility to handle the rename of this macro definition, in +// case they are used within userland code. +#ifdef NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#endif +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS +#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ + !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#define NAPI_DISABLE_CPP_EXCEPTIONS #endif // If C++ exceptions are not explicitly enabled or disabled, enable them // if exceptions were enabled in the compiler settings. -#if !defined(NAPI_CPP_EXCEPTIONS) && !defined(NAPI_DISABLE_CPP_EXCEPTIONS) - #if defined(_CPPUNWIND) || defined (__EXCEPTIONS) - #define NAPI_CPP_EXCEPTIONS - #else - #error Exception support not detected. \ - Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS. - #endif +#if !defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) +#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) +#define NODE_ADDON_API_CPP_EXCEPTIONS +#else +#error Exception support not detected. \ + Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. +#endif +#endif + +// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE +// should not be set +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NODE_ADDON_API_CPP_EXCEPTIONS is defined. #endif #ifdef _NOEXCEPT - #define NAPI_NOEXCEPT _NOEXCEPT +#define NAPI_NOEXCEPT _NOEXCEPT #else - #define NAPI_NOEXCEPT noexcept +#define NAPI_NOEXCEPT noexcept #endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are enabled, Errors are thrown directly. There is no need // to return anything after the throw statements. The variadic parameter is an @@ -48,16 +89,16 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 // We need _VOID versions of the macros to avoid warnings resulting from // leaving the NAPI_THROW_* `...` argument empty. -#define NAPI_THROW(e, ...) throw e -#define NAPI_THROW_VOID(e) throw e +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e -#define NAPI_THROW_IF_FAILED(env, status, ...) \ +#define NAPI_THROW_IF_FAILED(env, status, ...) \ if ((status) != napi_ok) throw Napi::Error::New(env); -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ if ((status) != napi_ok) throw Napi::Error::New(env); -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, // which are pending until the callback returns to JS. The variadic parameter @@ -65,2574 +106,3315 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 // We need _VOID versions of the macros to avoid warnings resulting from // leaving the NAPI_THROW_* `...` argument empty. -#define NAPI_THROW(e, ...) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ } while (0) -#define NAPI_THROW_VOID(e) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return; \ +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ } while (0) -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ } -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return; \ +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) -# define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; -# define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif -#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ - NAPI_DISALLOW_ASSIGN(CLASS) \ - NAPI_DISALLOW_COPY(CLASS) +#define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; +#define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; -#define NAPI_FATAL_IF_FAILED(status, location, message) \ - do { \ - if ((status) != napi_ok) { \ - Napi::Error::Fatal((location), (message)); \ - } \ +#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ + NAPI_DISALLOW_ASSIGN(CLASS) \ + NAPI_DISALLOW_COPY(CLASS) + +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ } while (0) +// Internal check helper. Be careful that the formatted message length should be +// max 255 size and null terminated. +#define NAPI_INTERNAL_CHECK(expr, location, ...) \ + do { \ + if (!(expr)) { \ + std::string msg = Napi::details::StringFormat(__VA_ARGS__); \ + Napi::Error::Fatal(location, msg.c_str()); \ + } \ + } while (0) + +#define NAPI_INTERNAL_CHECK_EQ(actual, expected, value_format, location) \ + do { \ + auto actual_value = (actual); \ + NAPI_INTERNAL_CHECK(actual_value == (expected), \ + location, \ + "Expected " #actual " to be equal to " #expected \ + ", but got " value_format ".", \ + actual_value); \ + } while (0) + +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + //////////////////////////////////////////////////////////////////////////////// -/// N-API C++ Wrapper Classes +/// Node-API C++ Wrapper Classes /// -/// These classes wrap the "N-API" ABI-stable C APIs for Node.js, providing a +/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a /// C++ object model and C++ exception-handling semantics with low overhead. /// The wrappers are all header-only so that they do not affect the ABI. //////////////////////////////////////////////////////////////////////////////// namespace Napi { - // Forward declarations - class Env; - class Value; - class Boolean; - class Number; +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +// NAPI_CPP_CUSTOM_NAMESPACE can be #define'd per-addon to avoid symbol +// conflicts between different instances of node-addon-api + +// First dummy definition of the namespace to make sure that Napi::(name) still +// refers to the right things inside this file. +namespace NAPI_CPP_CUSTOM_NAMESPACE {} +using namespace NAPI_CPP_CUSTOM_NAMESPACE; + +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Forward declarations +class Env; +class Value; +class Boolean; +class Number; #if NAPI_VERSION > 5 - class BigInt; +class BigInt; #endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) - class Date; +class Date; #endif - class String; - class Object; - class Array; - class ArrayBuffer; - class Function; - class Error; - class PropertyDescriptor; - class CallbackInfo; - class TypedArray; - template class TypedArrayOf; - - typedef TypedArrayOf Int8Array; ///< Typed-array of signed 8-bit integers - typedef TypedArrayOf Uint8Array; ///< Typed-array of unsigned 8-bit integers - typedef TypedArrayOf Int16Array; ///< Typed-array of signed 16-bit integers - typedef TypedArrayOf Uint16Array; ///< Typed-array of unsigned 16-bit integers - typedef TypedArrayOf Int32Array; ///< Typed-array of signed 32-bit integers - typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers - typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values - typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values +class String; +class Object; +class Array; +class ArrayBuffer; +class Function; +class Error; +class PropertyDescriptor; +class CallbackInfo; +class TypedArray; +template +class TypedArrayOf; + +using Int8Array = + TypedArrayOf; ///< Typed-array of signed 8-bit integers +using Uint8Array = + TypedArrayOf; ///< Typed-array of unsigned 8-bit integers +using Int16Array = + TypedArrayOf; ///< Typed-array of signed 16-bit integers +using Uint16Array = + TypedArrayOf; ///< Typed-array of unsigned 16-bit integers +using Int32Array = + TypedArrayOf; ///< Typed-array of signed 32-bit integers +using Uint32Array = + TypedArrayOf; ///< Typed-array of unsigned 32-bit integers +using Float32Array = + TypedArrayOf; ///< Typed-array of 32-bit floating-point values +using Float64Array = + TypedArrayOf; ///< Typed-array of 64-bit floating-point values #if NAPI_VERSION > 5 - typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers - typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers -#endif // NAPI_VERSION > 5 +using BigInt64Array = + TypedArrayOf; ///< Typed array of signed 64-bit integers +using BigUint64Array = + TypedArrayOf; ///< Typed array of unsigned 64-bit integers +#endif // NAPI_VERSION > 5 - /// Defines the signature of a N-API C++ module's registration callback (init) function. - typedef Object (*ModuleRegisterCallback)(Env env, Object exports); +/// Defines the signature of a Node-API C++ module's registration callback +/// (init) function. +using ModuleRegisterCallback = Object (*)(Env env, Object exports); - class MemoryManagement; +class MemoryManagement; - /// Environment for N-API values and operations. - /// - /// All N-API values and operations must be associated with an environment. An environment - /// instance is always provided to callback functions; that environment must then be used for any - /// creation of N-API values or other N-API operations within the callback. (Many methods infer - /// the environment from the `this` instance that the method is called on.) - /// - /// In the future, multiple environments per process may be supported, although current - /// implementations only support one environment per process. - /// - /// In the V8 JavaScript engine, a N-API environment approximately corresponds to an Isolate. - class Env { +/// A simple Maybe type, representing an object which may or may not have a +/// value. +/// +/// If an API method returns a Maybe<>, the API method can potentially fail +/// either because an exception is thrown, or because an exception is pending, +/// e.g. because a previous API call threw an exception that hasn't been +/// caught yet. In that case, a "Nothing" value is returned. +template +class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); +}; + +template +inline Maybe Nothing(); + +template +inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +template +using MaybeOrValue = Maybe; +#else +template +using MaybeOrValue = T; +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +using node_addon_api_basic_env = node_api_nogc_env; +using node_addon_api_basic_finalize = node_api_nogc_finalize; +#else +using node_addon_api_basic_env = napi_env; +using node_addon_api_basic_finalize = napi_finalize; +#endif + +/// Environment for Node-API values and operations. +/// +/// All Node-API values and operations must be associated with an environment. +/// An environment instance is always provided to callback functions; that +/// environment must then be used for any creation of Node-API values or other +/// Node-API operations within the callback. (Many methods infer the +/// environment from the `this` instance that the method is called on.) +/// +/// Multiple environments may co-exist in a single process or a thread. +/// +/// In the V8 JavaScript engine, a Node-API environment approximately +/// corresponds to an Isolate. +class BasicEnv { + private: + node_addon_api_basic_env _env; #if NAPI_VERSION > 5 - private: - template static void DefaultFini(Env, T* data); - template - static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); + template + static void DefaultFini(Env, T* data); + template + static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); #endif // NAPI_VERSION > 5 - public: - Env(napi_env env); - - operator napi_env() const; + public: + BasicEnv(node_addon_api_basic_env env); + + operator node_addon_api_basic_env() const; + + // Without these operator overloads, the error: + // + // Use of overloaded operator '==' is ambiguous (with operand types + // 'Napi::Env' and 'Napi::Env') + // + // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr + bool operator==(const BasicEnv& other) const { + return _env == other._env; + } + bool operator==(std::nullptr_t /*other*/) const { + return _env == nullptr; + } - Object Global() const; - Value Undefined() const; - Value Null() const; +#if NAPI_VERSION > 2 + template + class CleanupHook; - bool IsExceptionPending() const; - Error GetAndClearPendingException(); + template + CleanupHook AddCleanupHook(Hook hook); - Value RunScript(const char* utf8script); - Value RunScript(const std::string& utf8script); - Value RunScript(String script); + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 #if NAPI_VERSION > 5 - template T* GetInstanceData(); - - template using Finalizer = void (*)(Env, T*); - template fini = Env::DefaultFini> - void SetInstanceData(T* data); - - template - using FinalizerWithHint = void (*)(Env, DataType*, HintType*); - template fini = - Env::DefaultFiniWithHint> - void SetInstanceData(DataType* data, HintType* hint); -#endif // NAPI_VERSION > 5 - - private: - napi_env _env; - }; + template + T* GetInstanceData() const; - /// A JavaScript value of unknown type. - /// - /// For type-specific operations, convert to one of the Value subclasses using a `To*` or `As()` - /// method. The `To*` methods do type coercion; the `As()` method does not. - /// - /// Napi::Value value = ... - /// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid arg..."); - /// Napi::String str = value.As(); // Cast to a string value - /// - /// Napi::Value anotherValue = ... - /// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value - class Value { - public: - Value(); ///< Creates a new _empty_ Value instance. - Value(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - /// Creates a JS value from a C++ primitive. - /// - /// `value` may be any of: - /// - bool - /// - Any integer type - /// - Any floating point type - /// - const char* (encoded using UTF-8, null-terminated) - /// - const char16_t* (encoded using UTF-16-LE, null-terminated) - /// - std::string (encoded using UTF-8) - /// - std::u16string - /// - napi::Value - /// - napi_value - template - static Value From(napi_env env, const T& value); - - /// Converts to a N-API value primitive. - /// - /// If the instance is _empty_, this returns `nullptr`. - operator napi_value() const; - - /// Tests if this value strictly equals another value. - bool operator ==(const Value& other) const; - - /// Tests if this value does not strictly equal another value. - bool operator !=(const Value& other) const; - - /// Tests if this value strictly equals another value. - bool StrictEquals(const Value& other) const; - - /// Gets the environment the value is associated with. - Napi::Env Env() const; + template + using Finalizer = void (*)(Env, T*); + template fini = BasicEnv::DefaultFini> + void SetInstanceData(T* data) const; + + template + using FinalizerWithHint = void (*)(Env, DataType*, HintType*); + template fini = + BasicEnv::DefaultFiniWithHint> + void SetInstanceData(DataType* data, HintType* hint) const; +#endif // NAPI_VERSION > 5 - /// Checks if the value is empty (uninitialized). - /// - /// An empty value is invalid, and most attempts to perform an operation on an empty value - /// will result in an exception. Note an empty value is distinct from JavaScript `null` or - /// `undefined`, which are valid values. - /// - /// When C++ exceptions are disabled at compile time, a method with a `Value` return type may - /// return an empty value to indicate a pending exception. So when not using C++ exceptions, - /// callers should check whether the value is empty before attempting to use it. +#if NAPI_VERSION > 2 + template + class CleanupHook { + public: + CleanupHook(); + CleanupHook(BasicEnv env, Hook hook, Arg* arg); + CleanupHook(BasicEnv env, Hook hook); + bool Remove(BasicEnv env); bool IsEmpty() const; - napi_valuetype Type() const; ///< Gets the type of the value. - - bool IsUndefined() const; ///< Tests if a value is an undefined JavaScript value. - bool IsNull() const; ///< Tests if a value is a null JavaScript value. - bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. - bool IsNumber() const; ///< Tests if a value is a JavaScript number. -#if NAPI_VERSION > 5 - bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. -#endif // NAPI_VERSION > 5 -#if (NAPI_VERSION > 4) - bool IsDate() const; ///< Tests if a value is a JavaScript date. -#endif - bool IsString() const; ///< Tests if a value is a JavaScript string. - bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. - bool IsArray() const; ///< Tests if a value is a JavaScript array. - bool IsArrayBuffer() const; ///< Tests if a value is a JavaScript array buffer. - bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. - bool IsObject() const; ///< Tests if a value is a JavaScript object. - bool IsFunction() const; ///< Tests if a value is a JavaScript function. - bool IsPromise() const; ///< Tests if a value is a JavaScript promise. - bool IsDataView() const; ///< Tests if a value is a JavaScript data view. - bool IsBuffer() const; ///< Tests if a value is a Node buffer. - bool IsExternal() const; ///< Tests if a value is a pointer to external data. - - /// Casts to another type of `Napi::Value`, when the actual type is known or assumed. - /// - /// This conversion does NOT coerce the type. Calling any methods inappropriate for the actual - /// value type will throw `Napi::Error`. - template T As() const; - - Boolean ToBoolean() const; ///< Coerces a value to a JavaScript boolean. - Number ToNumber() const; ///< Coerces a value to a JavaScript number. - String ToString() const; ///< Coerces a value to a JavaScript string. - Object ToObject() const; ///< Coerces a value to a JavaScript object. - - protected: - /// !cond INTERNAL - napi_env _env; - napi_value _value; - /// !endcond + private: + static inline void Wrapper(void* data) NAPI_NOEXCEPT; + static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; + + void (*wrapper)(void* arg); + struct CleanupData { + Hook hook; + Arg* arg; + } * data; }; +#endif // NAPI_VERSION > 2 - /// A JavaScript boolean value. - class Boolean : public Value { - public: - static Boolean New( - napi_env env, ///< N-API environment - bool value ///< Boolean value - ); +#if NAPI_VERSION > 8 + const char* GetModuleFileName() const; +#endif // NAPI_VERSION > 8 - Boolean(); ///< Creates a new _empty_ Boolean instance. - Boolean(napi_env env, napi_value value); ///< Wraps a N-API value primitive. +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template + inline void PostFinalizer(FinalizerType finalizeCallback) const; - operator bool() const; ///< Converts a Boolean value to a boolean primitive. - bool Value() const; ///< Converts a Boolean value to a boolean primitive. - }; + template + inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; - /// A JavaScript number value. - class Number : public Value { - public: - static Number New( - napi_env env, ///< N-API environment - double value ///< Number value - ); - - Number(); ///< Creates a new _empty_ Number instance. - Number(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - operator int32_t() const; ///< Converts a Number value to a 32-bit signed integer value. - operator uint32_t() const; ///< Converts a Number value to a 32-bit unsigned integer value. - operator int64_t() const; ///< Converts a Number value to a 64-bit signed integer value. - operator float() const; ///< Converts a Number value to a 32-bit floating-point value. - operator double() const; ///< Converts a Number value to a 64-bit floating-point value. - - int32_t Int32Value() const; ///< Converts a Number value to a 32-bit signed integer value. - uint32_t Uint32Value() const; ///< Converts a Number value to a 32-bit unsigned integer value. - int64_t Int64Value() const; ///< Converts a Number value to a 64-bit signed integer value. - float FloatValue() const; ///< Converts a Number value to a 32-bit floating-point value. - double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. - }; + template + inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER -#if NAPI_VERSION > 5 - /// A JavaScript bigint value. - class BigInt : public Value { - public: - static BigInt New( - napi_env env, ///< N-API environment - int64_t value ///< Number value - ); - static BigInt New( - napi_env env, ///< N-API environment - uint64_t value ///< Number value - ); - - /// Creates a new BigInt object using a specified sign bit and a - /// specified list of digits/words. - /// The resulting number is calculated as: - /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) - static BigInt New( - napi_env env, ///< N-API environment - int sign_bit, ///< Sign bit. 1 if negative. - size_t word_count, ///< Number of words in array - const uint64_t* words ///< Array of words - ); - - BigInt(); ///< Creates a new _empty_ BigInt instance. - BigInt(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - int64_t Int64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit signed integer value. - uint64_t Uint64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit unsigned integer value. - - size_t WordCount() const; ///< The number of 64-bit words needed to store the result of ToWords(). - - /// Writes the contents of this BigInt to a specified memory location. - /// `sign_bit` must be provided and will be set to 1 if this BigInt is negative. - /// `*word_count` has to be initialized to the length of the `words` array. - /// Upon return, it will be set to the actual number of words that would - /// be needed to store this BigInt (i.e. the return value of `WordCount()`). - void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); - }; -#endif // NAPI_VERSION > 5 + friend class Env; +}; -#if (NAPI_VERSION > 4) - /// A JavaScript date value. - class Date : public Value { - public: - /// Creates a new Date value from a double primitive. - static Date New( - napi_env env, ///< N-API environment - double value ///< Number value - ); - - Date(); ///< Creates a new _empty_ Date instance. - Date(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - operator double() const; ///< Converts a Date value to double primitive - - double ValueOf() const; ///< Converts a Date value to a double primitive. - }; - #endif +class Env : public BasicEnv { + public: + Env(napi_env env); - /// A JavaScript string or symbol value (that can be used as a property name). - class Name : public Value { - public: - Name(); ///< Creates a new _empty_ Name instance. - Name(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - }; + operator napi_env() const; - /// A JavaScript string value. - class String : public Name { - public: - /// Creates a new String value from a UTF-8 encoded C++ string. - static String New( - napi_env env, ///< N-API environment - const std::string& value ///< UTF-8 encoded C++ string - ); - - /// Creates a new String value from a UTF-16 encoded C++ string. - static String New( - napi_env env, ///< N-API environment - const std::u16string& value ///< UTF-16 encoded C++ string - ); - - /// Creates a new String value from a UTF-8 encoded C string. - static String New( - napi_env env, ///< N-API environment - const char* value ///< UTF-8 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-16 encoded C string. - static String New( - napi_env env, ///< N-API environment - const char16_t* value ///< UTF-16 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-8 encoded C string with specified length. - static String New( - napi_env env, ///< N-API environment - const char* value, ///< UTF-8 encoded C string (not necessarily null-terminated) - size_t length ///< length of the string in bytes - ); - - /// Creates a new String value from a UTF-16 encoded C string with specified length. - static String New( - napi_env env, ///< N-API environment - const char16_t* value, ///< UTF-16 encoded C string (not necessarily null-terminated) - size_t length ///< Length of the string in 2-byte code units - ); - - /// Creates a new String based on the original object's type. - /// - /// `value` may be any of: - /// - const char* (encoded using UTF-8, null-terminated) - /// - const char16_t* (encoded using UTF-16-LE, null-terminated) - /// - std::string (encoded using UTF-8) - /// - std::u16string - template - static String From(napi_env env, const T& value); - - String(); ///< Creates a new _empty_ String instance. - String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - operator std::string() const; ///< Converts a String value to a UTF-8 encoded C++ string. - operator std::u16string() const; ///< Converts a String value to a UTF-16 encoded C++ string. - std::string Utf8Value() const; ///< Converts a String value to a UTF-8 encoded C++ string. - std::u16string Utf16Value() const; ///< Converts a String value to a UTF-16 encoded C++ string. - }; + Object Global() const; + Value Undefined() const; + Value Null() const; - /// A JavaScript symbol value. - class Symbol : public Name { - public: - /// Creates a new Symbol value with an optional description. - static Symbol New( - napi_env env, ///< N-API environment - const char* description = nullptr ///< Optional UTF-8 encoded null-terminated C string - /// describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - const std::string& description ///< UTF-8 encoded C++ string describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - String description ///< String value describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - napi_value description ///< String value describing the symbol - ); - - /// Get a public Symbol (e.g. Symbol.iterator). - static Symbol WellKnown(napi_env, const std::string& name); - - Symbol(); ///< Creates a new _empty_ Symbol instance. - Symbol(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - }; + bool IsExceptionPending() const; + Error GetAndClearPendingException() const; - /// A JavaScript object value. - class Object : public Value { - public: - /// Enables property and element assignments using indexing syntax. - /// - /// Example: - /// - /// Napi::Value propertyValue = object1['A']; - /// object2['A'] = propertyValue; - /// Napi::Value elementValue = array[0]; - /// array[1] = elementValue; - template - class PropertyLValue { - public: - /// Converts an L-value to a value. - operator Value() const; - - /// Assigns a value to the property. The type of value can be - /// anything supported by `Object::Set`. - template - PropertyLValue& operator =(ValueType value); - - private: - PropertyLValue() = delete; - PropertyLValue(Object object, Key key); - napi_env _env; - napi_value _object; - Key _key; - - friend class Napi::Object; - }; - - /// Creates a new Object value. - static Object New( - napi_env env ///< N-API environment - ); - - Object(); ///< Creates a new _empty_ Object instance. - Object(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - /// Gets or sets a named property. - PropertyLValue operator []( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ); - - /// Gets or sets a named property. - PropertyLValue operator []( - const std::string& utf8name ///< UTF-8 encoded property name - ); - - /// Gets or sets an indexed property or array element. - PropertyLValue operator []( - uint32_t index /// Property / element index - ); - - /// Gets a named property. - Value operator []( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Gets a named property. - Value operator []( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Gets an indexed property or array element. - Value operator []( - uint32_t index ///< Property / element index - ) const; - - /// Checks whether a property is present. - bool Has( - napi_value key ///< Property key primitive - ) const; - - /// Checks whether a property is present. - bool Has( - Value key ///< Property key - ) const; - - /// Checks whether a named property is present. - bool Has( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Checks whether a named property is present. - bool Has( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Checks whether a own property is present. - bool HasOwnProperty( - napi_value key ///< Property key primitive - ) const; - - /// Checks whether a own property is present. - bool HasOwnProperty( - Value key ///< Property key - ) const; - - /// Checks whether a own property is present. - bool HasOwnProperty( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Checks whether a own property is present. - bool HasOwnProperty( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Gets a property. - Value Get( - napi_value key ///< Property key primitive - ) const; - - /// Gets a property. - Value Get( - Value key ///< Property key - ) const; - - /// Gets a named property. - Value Get( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ) const; - - /// Gets a named property. - Value Get( - const std::string& utf8name ///< UTF-8 encoded property name - ) const; - - /// Sets a property. - template - void Set( - napi_value key, ///< Property key primitive - const ValueType& value ///< Property value primitive - ); + MaybeOrValue RunScript(const char* utf8script) const; + MaybeOrValue RunScript(const std::string& utf8script) const; + MaybeOrValue RunScript(String script) const; +}; - /// Sets a property. - template - void Set( - Value key, ///< Property key - const ValueType& value ///< Property value - ); +/// A JavaScript value of unknown type. +/// +/// For type-specific operations, convert to one of the Value subclasses using a +/// `To*` or `As()` method. The `To*` methods do type coercion; the `As()` +/// method does not. +/// +/// Napi::Value value = ... +/// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid +/// arg..."); Napi::String str = value.As(); // Cast to a +/// string value +/// +/// Napi::Value anotherValue = ... +/// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value +class Value { + public: + Value(); ///< Creates a new _empty_ Value instance. + Value(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Creates a JS value from a C++ primitive. + /// + /// `value` may be any of: + /// - bool + /// - Any integer type + /// - Any floating point type + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + /// - napi::Value + /// - napi_value + template + static Value From(napi_env env, const T& value); - /// Sets a named property. - template - void Set( - const char* utf8name, ///< UTF-8 encoded null-terminated property name - const ValueType& value - ); + static void CheckCast(napi_env env, napi_value value); - /// Sets a named property. - template - void Set( - const std::string& utf8name, ///< UTF-8 encoded property name - const ValueType& value ///< Property value primitive - ); - - /// Delete property. - bool Delete( - napi_value key ///< Property key primitive - ); - - /// Delete property. - bool Delete( - Value key ///< Property key - ); - - /// Delete property. - bool Delete( - const char* utf8name ///< UTF-8 encoded null-terminated property name - ); - - /// Delete property. - bool Delete( - const std::string& utf8name ///< UTF-8 encoded property name - ); - - /// Checks whether an indexed property is present. - bool Has( - uint32_t index ///< Property / element index - ) const; - - /// Gets an indexed property or array element. - Value Get( - uint32_t index ///< Property / element index - ) const; - - /// Sets an indexed property or array element. - template - void Set( - uint32_t index, ///< Property / element index - const ValueType& value ///< Property value primitive - ); + /// Converts to a Node-API value primitive. + /// + /// If the instance is _empty_, this returns `nullptr`. + operator napi_value() const; - /// Deletes an indexed property or array element. - bool Delete( - uint32_t index ///< Property / element index - ); + /// Tests if this value strictly equals another value. + bool operator==(const Value& other) const; - Array GetPropertyNames() const; ///< Get all property names + /// Tests if this value does not strictly equal another value. + bool operator!=(const Value& other) const; - /// Defines a property on the object. - void DefineProperty( - const PropertyDescriptor& property ///< Descriptor for the property to be defined - ); + /// Tests if this value strictly equals another value. + bool StrictEquals(const Value& other) const; - /// Defines properties on the object. - void DefineProperties( - const std::initializer_list& properties - ///< List of descriptors for the properties to be defined - ); + /// Gets the environment the value is associated with. + Napi::Env Env() const; - /// Defines properties on the object. - void DefineProperties( - const std::vector& properties - ///< Vector of descriptors for the properties to be defined - ); + /// Checks if the value is empty (uninitialized). + /// + /// An empty value is invalid, and most attempts to perform an operation on an + /// empty value will result in an exception. Note an empty value is distinct + /// from JavaScript `null` or `undefined`, which are valid values. + /// + /// When C++ exceptions are disabled at compile time, a method with a `Value` + /// return type may return an empty value to indicate a pending exception. So + /// when not using C++ exceptions, callers should check whether the value is + /// empty before attempting to use it. + bool IsEmpty() const; + + napi_valuetype Type() const; ///< Gets the type of the value. + + bool IsUndefined() + const; ///< Tests if a value is an undefined JavaScript value. + bool IsNull() const; ///< Tests if a value is a null JavaScript value. + bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. + bool IsNumber() const; ///< Tests if a value is a JavaScript number. +#if NAPI_VERSION > 5 + bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) + bool IsDate() const; ///< Tests if a value is a JavaScript date. +#endif + bool IsString() const; ///< Tests if a value is a JavaScript string. + bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. + bool IsArray() const; ///< Tests if a value is a JavaScript array. + bool IsArrayBuffer() + const; ///< Tests if a value is a JavaScript array buffer. + bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. + bool IsObject() const; ///< Tests if a value is a JavaScript object. + bool IsFunction() const; ///< Tests if a value is a JavaScript function. + bool IsPromise() const; ///< Tests if a value is a JavaScript promise. + bool IsDataView() const; ///< Tests if a value is a JavaScript data view. + bool IsBuffer() const; ///< Tests if a value is a Node buffer. + bool IsExternal() const; ///< Tests if a value is a pointer to external data. +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + bool IsSharedArrayBuffer() const; +#endif - /// Checks if an object is an instance created by a constructor function. - /// - /// This is equivalent to the JavaScript `instanceof` operator. - bool InstanceOf( - const Function& constructor ///< Constructor function - ) const; + /// Casts to another type of `Napi::Value`, when the actual type is known or + /// assumed. + /// + /// This conversion does NOT coerce the type. Calling any methods + /// inappropriate for the actual value type will throw `Napi::Error`. + /// + /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method + /// asserts that the actual type is the expected type. + template + T As() const; - template - inline void AddFinalizer(Finalizer finalizeCallback, T* data); + // Unsafe Value::As(), should be avoided. + template + T UnsafeAs() const; + + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. + + protected: + /// !cond INTERNAL + napi_env _env; + napi_value _value; + /// !endcond +}; + +/// A JavaScript boolean value. +class Boolean : public Value { + public: + static Boolean New(napi_env env, ///< Node-API environment + bool value ///< Boolean value + ); + + static void CheckCast(napi_env env, napi_value value); + + Boolean(); ///< Creates a new _empty_ Boolean instance. + Boolean(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator bool() const; ///< Converts a Boolean value to a boolean primitive. + bool Value() const; ///< Converts a Boolean value to a boolean primitive. +}; + +/// A JavaScript number value. +class Number : public Value { + public: + static Number New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Number(); ///< Creates a new _empty_ Number instance. + Number(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator int32_t() + const; ///< Converts a Number value to a 32-bit signed integer value. + operator uint32_t() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + operator int64_t() + const; ///< Converts a Number value to a 64-bit signed integer value. + operator float() + const; ///< Converts a Number value to a 32-bit floating-point value. + operator double() + const; ///< Converts a Number value to a 64-bit floating-point value. + + int32_t Int32Value() + const; ///< Converts a Number value to a 32-bit signed integer value. + uint32_t Uint32Value() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + int64_t Int64Value() + const; ///< Converts a Number value to a 64-bit signed integer value. + float FloatValue() + const; ///< Converts a Number value to a 32-bit floating-point value. + double DoubleValue() + const; ///< Converts a Number value to a 64-bit floating-point value. +}; - template - inline void AddFinalizer(Finalizer finalizeCallback, - T* data, - Hint* finalizeHint); - }; +#if NAPI_VERSION > 5 +/// A JavaScript bigint value. +class BigInt : public Value { + public: + static BigInt New(napi_env env, ///< Node-API environment + int64_t value ///< Number value + ); + static BigInt New(napi_env env, ///< Node-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New(napi_env env, ///< Node-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + static void CheckCast(napi_env env, napi_value value); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + int64_t Int64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store + ///< the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is + /// negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); +}; +#endif // NAPI_VERSION > 5 - template - class External : public Value { - public: - static External New(napi_env env, T* data); - - // Finalizer must implement `void operator()(Env env, T* data)`. - template - static External New(napi_env env, - T* data, - Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - template - static External New(napi_env env, - T* data, - Finalizer finalizeCallback, - Hint* finalizeHint); - - External(); - External(napi_env env, napi_value value); - - T* Data() const; - }; +#if (NAPI_VERSION > 4) +/// A JavaScript date value. +class Date : public Value { + public: + /// Creates a new Date value from a double primitive. + static Date New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + /// Creates a new Date value from a std::chrono::system_clock::time_point. + static Date New( + napi_env env, ///< Node-API environment + std::chrono::system_clock::time_point time_point ///< Time point value + ); + + static void CheckCast(napi_env env, napi_value value); + + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + operator double() const; ///< Converts a Date value to double primitive + + double ValueOf() const; ///< Converts a Date value to a double primitive. +}; +#endif - class Array : public Object { - public: - static Array New(napi_env env); - static Array New(napi_env env, size_t length); +/// A JavaScript string or symbol value (that can be used as a property name). +class Name : public Value { + public: + static void CheckCast(napi_env env, napi_value value); + + Name(); ///< Creates a new _empty_ Name instance. + Name(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +/// A JavaScript string value. +class String : public Name { + public: + /// Creates a new String value from a UTF-8 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::string& value ///< UTF-8 encoded C++ string + ); + + /// Creates a new String value from a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::u16string& value ///< UTF-16 encoded C++ string + ); + + /// Creates a new String value from a UTF-8 encoded C++ string view. + static String New(napi_env env, ///< Node-API environment + std::string_view value ///< UTF-8 encoded C++ string view + ); + + /// Creates a new String value from a UTF-8 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char* value ///< UTF-8 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-16 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value ///< UTF-16 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-8 encoded C string with specified + /// length. + static String New(napi_env env, ///< Node-API environment + const char* value, ///< UTF-8 encoded C string (not + ///< necessarily null-terminated) + size_t length ///< length of the string in bytes + ); + + /// Creates a new String value from a UTF-16 encoded C string with specified + /// length. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value, ///< UTF-16 encoded C string (not necessarily + ///< null-terminated) + size_t length ///< Length of the string in 2-byte code units + ); + + /// Creates a new String based on the original object's type. + /// + /// `value` may be any of: + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + template + static String From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + String(); ///< Creates a new _empty_ String instance. + String(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator std::string() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + operator std::u16string() + const; ///< Converts a String value to a UTF-16 encoded C++ string. + std::string Utf8Value() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + std::u16string Utf16Value() + const; ///< Converts a String value to a UTF-16 encoded C++ string. +}; + +/// A JavaScript symbol value. +class Symbol : public Name { + public: + /// Creates a new Symbol value with an optional description. + static Symbol New( + napi_env env, ///< Node-API environment + const char* description = + nullptr ///< Optional UTF-8 encoded null-terminated C string + /// describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + const std::string& + description ///< UTF-8 encoded C++ string describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + std::string_view + description ///< UTF-8 encoded C++ string view describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New(napi_env env, ///< Node-API environment + String description ///< String value describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + napi_value description ///< String value describing the symbol + ); + + /// Get a public Symbol (e.g. Symbol.iterator). + static MaybeOrValue WellKnown(napi_env, const std::string& name); + + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static MaybeOrValue For(napi_env env, const std::string& description); + + // Create a symbol in the global registry, UTF-8 encoded cpp string view + static MaybeOrValue For(napi_env env, std::string_view description); + + // Create a symbol in the global registry, C style string (null terminated) + static MaybeOrValue For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static MaybeOrValue For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static MaybeOrValue For(napi_env env, napi_value description); + + static void CheckCast(napi_env env, napi_value value); + + Symbol(); ///< Creates a new _empty_ Symbol instance. + Symbol(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +class TypeTaggable : public Value { + public: +#if NAPI_VERSION >= 8 + void TypeTag(const napi_type_tag* type_tag) const; + bool CheckTypeTag(const napi_type_tag* type_tag) const; +#endif // NAPI_VERSION >= 8 + protected: + TypeTaggable(); + TypeTaggable(napi_env env, napi_value value); +}; + +/// A JavaScript object value. +class Object : public TypeTaggable { + public: + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { + public: + /// Converts an L-value to a value. + operator Value() const; - Array(); - Array(napi_env env, napi_value value); + /// Assigns a value to the property. The type of value can be + /// anything supported by `Object::Set`. + template + PropertyLValue& operator=(ValueType value); - uint32_t Length() const; - }; + /// Converts an L-value to a value. For convenience. + Value AsValue() const; - /// A JavaScript array buffer value. - class ArrayBuffer : public Object { - public: - /// Creates a new ArrayBuffer instance over a new automatically-allocated buffer. - static ArrayBuffer New( - napi_env env, ///< N-API environment - size_t byteLength ///< Length of the buffer to be allocated, in bytes - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength ///< Length of the external buffer to be used by the array, in bytes - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength, ///< Length of the external buffer to be used by the array, - /// in bytes - Finalizer finalizeCallback ///< Function to be called when the array buffer is destroyed; - /// must implement `void operator()(Env env, void* externalData)` - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength, ///< Length of the external buffer to be used by the array, - /// in bytes - Finalizer finalizeCallback, ///< Function to be called when the array buffer is destroyed; - /// must implement `void operator()(Env env, void* externalData, Hint* hint)` - Hint* finalizeHint ///< Hint (second parameter) to be passed to the finalize callback - ); - - ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. - ArrayBuffer(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - void* Data(); ///< Gets a pointer to the data buffer. - size_t ByteLength(); ///< Gets the length of the array buffer in bytes. + private: + PropertyLValue() = delete; + PropertyLValue(Object object, Key key); + napi_env _env; + napi_value _object; + Key _key; -#if NAPI_VERSION >= 7 - bool IsDetached() const; - void Detach(); -#endif // NAPI_VERSION >= 7 + friend class Napi::Object; }; - /// A JavaScript typed-array value with unknown array type. + /// Creates a new Object value. + static Object New(napi_env env ///< Node-API environment + ); + + static void CheckCast(napi_env env, napi_value value); + + Object(); ///< Creates a new _empty_ Object instance. + Object(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Gets or sets a named property. + PropertyLValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ); + + /// Gets or sets a named property. + PropertyLValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[]( + uint32_t index /// Property / element index + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue operator[](uint32_t index ///< Property / element index + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(Value key ///< Property key + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(Value key ///< Property key + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets a property. + MaybeOrValue Get(napi_value key ///< Property key primitive + ) const; + + /// Gets a property. + MaybeOrValue Get(Value key ///< Property key + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Sets a property. + template + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive + ) const; + + /// Sets a property. + template + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value + ) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const char* utf8name, ///< UTF-8 encoded null-terminated property name + const ValueType& value) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(napi_value key ///< Property key primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(Value key ///< Property key + ) const; + + /// Delete property. + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Delete property. + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether an indexed property is present. + MaybeOrValue Has(uint32_t index ///< Property / element index + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue Get(uint32_t index ///< Property / element index + ) const; + + /// Sets an indexed property or array element. + template + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive + ) const; + + /// Deletes an indexed property or array element. + MaybeOrValue Delete(uint32_t index ///< Property / element index + ) const; + + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names + + /// Defines a property on the object. /// - /// For type-specific operations, cast to a `TypedArrayOf` instance using the `As()` - /// method: + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( + const PropertyDescriptor& + property ///< Descriptor for the property to be defined + ) const; + + /// Defines properties on the object. /// - /// Napi::TypedArray array = ... - /// if (t.TypedArrayType() == napi_int32_array) { - /// Napi::Int32Array int32Array = t.As(); - /// } - class TypedArray : public Object { - public: - TypedArray(); ///< Creates a new _empty_ TypedArray instance. - TypedArray(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - napi_typedarray_type TypedArrayType() const; ///< Gets the type of this typed-array. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. - - uint8_t ElementSize() const; ///< Gets the size in bytes of one element in the array. - size_t ElementLength() const; ///< Gets the number of elements in the array. - size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. - size_t ByteLength() const; ///< Gets the length of the array in bytes. - - protected: - /// !cond INTERNAL - napi_typedarray_type _type; - size_t _length; - - TypedArray(napi_env env, napi_value value, napi_typedarray_type type, size_t length); - - static const napi_typedarray_type unknown_array_type = static_cast(-1); - - template - static -#if defined(NAPI_HAS_CONSTEXPR) - constexpr -#endif - napi_typedarray_type TypedArrayTypeForPrimitiveType() { - return std::is_same::value ? napi_int8_array - : std::is_same::value ? napi_uint8_array - : std::is_same::value ? napi_int16_array - : std::is_same::value ? napi_uint16_array - : std::is_same::value ? napi_int32_array - : std::is_same::value ? napi_uint32_array - : std::is_same::value ? napi_float32_array - : std::is_same::value ? napi_float64_array -#if NAPI_VERSION > 5 - : std::is_same::value ? napi_bigint64_array - : std::is_same::value ? napi_biguint64_array -#endif // NAPI_VERSION > 5 - : unknown_array_type; - } - /// !endcond - }; + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::initializer_list& properties + ///< List of descriptors for the properties to be defined + ) const; - /// A JavaScript typed-array value with known array type. + /// Defines properties on the object. /// - /// Note while it is possible to create and access Uint8 "clamped" arrays using this class, - /// the _clamping_ behavior is only applied in JavaScript. - template - class TypedArrayOf : public TypedArray { - public: - /// Creates a new TypedArray instance over a new automatically-allocated array buffer. - /// - /// The array type parameter can normally be omitted (because it is inferred from the template - /// parameter T), except when creating a "clamped" array: - /// - /// Uint8Array::New(env, length, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< N-API environment - size_t elementLength, ///< Length of the created array, as a number of elements -#if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() -#else - napi_typedarray_type type -#endif - ///< Type of array, if different from the default array type for the template parameter T. - ); - - /// Creates a new TypedArray instance over a provided array buffer. - /// - /// The array type parameter can normally be omitted (because it is inferred from the template - /// parameter T), except when creating a "clamped" array: - /// - /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< N-API environment - size_t elementLength, ///< Length of the created array, as a number of elements - Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use - size_t bufferOffset, ///< Offset into the array buffer where the typed-array starts -#if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() -#else - napi_typedarray_type type -#endif - ///< Type of array, if different from the default array type for the template parameter T. - ); - - TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. - TypedArrayOf(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - T& operator [](size_t index); ///< Gets or sets an element in the array. - const T& operator [](size_t index) const; ///< Gets an element in the array. - - /// Gets a pointer to the array's backing buffer. - /// - /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, because the - /// typed-array may have a non-zero `ByteOffset()` into the `ArrayBuffer`. - T* Data(); - - /// Gets a pointer to the array's backing buffer. - /// - /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, because the - /// typed-array may have a non-zero `ByteOffset()` into the `ArrayBuffer`. - const T* Data() const; - - private: - T* _data; - - TypedArrayOf(napi_env env, - napi_value value, - napi_typedarray_type type, - size_t length, - T* data); - }; + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::vector& properties + ///< Vector of descriptors for the properties to be defined + ) const; - /// The DataView provides a low-level interface for reading/writing multiple - /// number types in an ArrayBuffer irrespective of the platform's endianness. - class DataView : public Object { - public: - static DataView New(napi_env env, - Napi::ArrayBuffer arrayBuffer); - static DataView New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset); - static DataView New(napi_env env, - Napi::ArrayBuffer arrayBuffer, - size_t byteOffset, - size_t byteLength); - - DataView(); ///< Creates a new _empty_ DataView instance. - DataView(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. - size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. - size_t ByteLength() const; ///< Gets the length of the array in bytes. - - void* Data() const; - - float GetFloat32(size_t byteOffset) const; - double GetFloat64(size_t byteOffset) const; - int8_t GetInt8(size_t byteOffset) const; - int16_t GetInt16(size_t byteOffset) const; - int32_t GetInt32(size_t byteOffset) const; - uint8_t GetUint8(size_t byteOffset) const; - uint16_t GetUint16(size_t byteOffset) const; - uint32_t GetUint32(size_t byteOffset) const; - - void SetFloat32(size_t byteOffset, float value) const; - void SetFloat64(size_t byteOffset, double value) const; - void SetInt8(size_t byteOffset, int8_t value) const; - void SetInt16(size_t byteOffset, int16_t value) const; - void SetInt32(size_t byteOffset, int32_t value) const; - void SetUint8(size_t byteOffset, uint8_t value) const; - void SetUint16(size_t byteOffset, uint16_t value) const; - void SetUint32(size_t byteOffset, uint32_t value) const; - - private: - template - T ReadData(size_t byteOffset) const; - - template - void WriteData(size_t byteOffset, T value) const; - - void* _data; - size_t _length; - }; + /// Checks if an object is an instance created by a constructor function. + /// + /// This is equivalent to the JavaScript `instanceof` operator. + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function + ) const; - class Function : public Object { - public: - typedef void (*VoidCallback)(const CallbackInfo& info); - typedef Value (*Callback)(const CallbackInfo& info); - - template - static Function New(napi_env env, - const char* utf8name = nullptr, - void* data = nullptr); - - template - static Function New(napi_env env, - const char* utf8name = nullptr, - void* data = nullptr); - - template - static Function New(napi_env env, - const std::string& utf8name, - void* data = nullptr); - - template - static Function New(napi_env env, - const std::string& utf8name, - void* data = nullptr); - - /// Callable must implement operator() accepting a const CallbackInfo& - /// and return either void or Value. - template - static Function New(napi_env env, - Callable cb, - const char* utf8name = nullptr, - void* data = nullptr); - /// Callable must implement operator() accepting a const CallbackInfo& - /// and return either void or Value. - template - static Function New(napi_env env, - Callable cb, - const std::string& utf8name, - void* data = nullptr); - - Function(); - Function(napi_env env, napi_value value); - - Value operator ()(const std::initializer_list& args) const; - - Value Call(const std::initializer_list& args) const; - Value Call(const std::vector& args) const; - Value Call(size_t argc, const napi_value* args) const; - Value Call(napi_value recv, const std::initializer_list& args) const; - Value Call(napi_value recv, const std::vector& args) const; - Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; - Object New(size_t argc, const napi_value* args) const; - }; + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; - class Promise : public Object { - public: - class Deferred { - public: - static Deferred New(napi_env env); - Deferred(napi_env env); + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const; - Napi::Promise Promise() const; - Napi::Env Env() const; +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + class const_iterator; - void Resolve(napi_value value) const; - void Reject(napi_value value) const; + inline const_iterator begin() const; - private: - napi_env _env; - napi_deferred _deferred; - napi_value _promise; - }; + inline const_iterator end() const; - Promise(napi_env env, napi_value value); - }; + class iterator; - template - class Buffer : public Uint8Array { - public: - static Buffer New(napi_env env, size_t length); - static Buffer New(napi_env env, T* data, size_t length); - - // Finalizer must implement `void operator()(Env env, T* data)`. - template - static Buffer New(napi_env env, T* data, - size_t length, - Finalizer finalizeCallback); - // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. - template - static Buffer New(napi_env env, T* data, - size_t length, - Finalizer finalizeCallback, - Hint* finalizeHint); - - static Buffer Copy(napi_env env, const T* data, size_t length); - - Buffer(); - Buffer(napi_env env, napi_value value); - size_t Length() const; - T* Data() const; - - private: - mutable size_t _length; - mutable T* _data; - - Buffer(napi_env env, napi_value value, size_t length, T* data); - void EnsureInfo() const; - }; + inline iterator begin(); - /// Holds a counted reference to a value; initially a weak reference unless otherwise specified, - /// may be changed to/from a strong reference by adjusting the refcount. - /// - /// The referenced value is not immediately destroyed when the reference count is zero; it is - /// merely then eligible for garbage-collection if there are no other references to the value. - template - class Reference { - public: - static Reference New(const T& value, uint32_t initialRefcount = 0); + inline iterator end(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS - Reference(); - Reference(napi_env env, napi_ref ref); - ~Reference(); +#if NAPI_VERSION >= 8 + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze() const; + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal() const; +#endif // NAPI_VERSION >= 8 - // A reference can be moved but cannot be copied. - Reference(Reference&& other); - Reference& operator =(Reference&& other); - NAPI_DISALLOW_ASSIGN(Reference) + MaybeOrValue GetPrototype() const; - operator napi_ref() const; - bool operator ==(const Reference &other) const; - bool operator !=(const Reference &other) const; +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + MaybeOrValue SetPrototype(const Object& value) const; +#endif +}; - Napi::Env Env() const; - bool IsEmpty() const; +template +class External : public TypeTaggable { + public: + static External New(napi_env env, T* data); - // Note when getting the value of a Reference it is usually correct to do so - // within a HandleScope so that the value handle gets cleaned up efficiently. - T Value() const; + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static External New(napi_env env, T* data, Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static External New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint); - uint32_t Ref(); - uint32_t Unref(); - void Reset(); - void Reset(const T& value, uint32_t refcount = 0); + static void CheckCast(napi_env env, napi_value value); - // Call this on a reference that is declared as static data, to prevent its destructor - // from running at program shutdown time, which would attempt to reset the reference when - // the environment is no longer valid. - void SuppressDestruct(); + External(); + External(napi_env env, napi_value value); - protected: - Reference(const Reference&); + T* Data() const; +}; - /// !cond INTERNAL - napi_env _env; - napi_ref _ref; - /// !endcond +class Array : public Object { + public: + static Array New(napi_env env); + static Array New(napi_env env, size_t length); - private: - bool _suppressDestruct; - }; + static void CheckCast(napi_env env, napi_value value); - class ObjectReference: public Reference { - public: - ObjectReference(); - ObjectReference(napi_env env, napi_ref ref); - - // A reference can be moved but cannot be copied. - ObjectReference(Reference&& other); - ObjectReference& operator =(Reference&& other); - ObjectReference(ObjectReference&& other); - ObjectReference& operator =(ObjectReference&& other); - NAPI_DISALLOW_ASSIGN(ObjectReference) - - Napi::Value Get(const char* utf8name) const; - Napi::Value Get(const std::string& utf8name) const; - void Set(const char* utf8name, napi_value value); - void Set(const char* utf8name, Napi::Value value); - void Set(const char* utf8name, const char* utf8value); - void Set(const char* utf8name, bool boolValue); - void Set(const char* utf8name, double numberValue); - void Set(const std::string& utf8name, napi_value value); - void Set(const std::string& utf8name, Napi::Value value); - void Set(const std::string& utf8name, std::string& utf8value); - void Set(const std::string& utf8name, bool boolValue); - void Set(const std::string& utf8name, double numberValue); - - Napi::Value Get(uint32_t index) const; - void Set(uint32_t index, const napi_value value); - void Set(uint32_t index, const Napi::Value value); - void Set(uint32_t index, const char* utf8value); - void Set(uint32_t index, const std::string& utf8value); - void Set(uint32_t index, bool boolValue); - void Set(uint32_t index, double numberValue); - - protected: - ObjectReference(const ObjectReference&); - }; + Array(); + Array(napi_env env, napi_value value); - class FunctionReference: public Reference { - public: - FunctionReference(); - FunctionReference(napi_env env, napi_ref ref); - - // A reference can be moved but cannot be copied. - FunctionReference(Reference&& other); - FunctionReference& operator =(Reference&& other); - FunctionReference(FunctionReference&& other); - FunctionReference& operator =(FunctionReference&& other); - NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) - - Napi::Value operator ()(const std::initializer_list& args) const; - - Napi::Value Call(const std::initializer_list& args) const; - Napi::Value Call(const std::vector& args) const; - Napi::Value Call(napi_value recv, const std::initializer_list& args) const; - Napi::Value Call(napi_value recv, const std::vector& args) const; - Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Napi::Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; - }; + uint32_t Length() const; +}; - // Shortcuts to creating a new reference with inferred type and refcount = 0. - template Reference Weak(T value); - ObjectReference Weak(Object value); - FunctionReference Weak(Function value); +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +class Object::const_iterator { + private: + enum class Type { BEGIN, END }; - // Shortcuts to creating a new reference with inferred type and refcount = 1. - template Reference Persistent(T value); - ObjectReference Persistent(Object value); - FunctionReference Persistent(Function value); + inline const_iterator(const Object* object, const Type type); - /// A persistent reference to a JavaScript error object. Use of this class depends somewhat - /// on whether C++ exceptions are enabled at compile time. - /// - /// ### Handling Errors With C++ Exceptions - /// - /// If C++ exceptions are enabled, then the `Error` class extends `std::exception` and enables - /// integrated error-handling for C++ exceptions and JavaScript exceptions. - /// - /// If a N-API call fails without executing any JavaScript code (for example due to an invalid - /// argument), then the N-API wrapper automatically converts and throws the error as a C++ - /// exception of type `Napi::Error`. Or if a JavaScript function called by C++ code via N-API - /// throws a JavaScript exception, then the N-API wrapper automatically converts and throws it as - /// a C++ exception of type `Napi::Error`. - /// - /// If a C++ exception of type `Napi::Error` escapes from a N-API C++ callback, then the N-API - /// wrapper automatically converts and throws it as a JavaScript exception. Therefore, catching - /// a C++ exception of type `Napi::Error` prevents a JavaScript exception from being thrown. - /// - /// #### Example 1A - Throwing a C++ exception: - /// - /// Napi::Env env = ... - /// throw Napi::Error::New(env, "Example exception"); - /// - /// Following C++ statements will not be executed. The exception will bubble up as a C++ - /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically propataged as a JavaScript exception when the callback returns to JavaScript. - /// - /// #### Example 2A - Propagating a N-API C++ exception: - /// - /// Napi::Function jsFunctionThatThrows = someObj.As(); - /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); - /// - /// Following C++ statements will not be executed. The exception will bubble up as a C++ - /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically propagated as a JavaScript exception when the callback returns to JavaScript. - /// - /// #### Example 3A - Handling a N-API C++ exception: - /// - /// Napi::Function jsFunctionThatThrows = someObj.As(); - /// Napi::Value result; - /// try { - /// result = jsFunctionThatThrows({ arg1, arg2 }); - /// } catch (const Napi::Error& e) { - /// cerr << "Caught JavaScript exception: " + e.what(); - /// } - /// - /// Since the exception was caught here, it will not be propagated as a JavaScript exception. - /// - /// ### Handling Errors Without C++ Exceptions - /// - /// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) then this class - /// does not extend `std::exception`, and APIs in the `Napi` namespace do not throw C++ - /// exceptions when they fail. Instead, they raise _pending_ JavaScript exceptions and - /// return _empty_ `Value`s. Calling code should check `Value::IsEmpty()` before attempting - /// to use a returned value, and may use methods on the `Env` class to check for, get, and - /// clear a pending JavaScript exception. If the pending exception is not cleared, it will - /// be thrown when the native callback returns to JavaScript. - /// - /// #### Example 1B - Throwing a JS exception - /// - /// Napi::Env env = ... - /// Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); - /// return; - /// - /// After throwing a JS exception, the code should generally return immediately from the native - /// callback, after performing any necessary cleanup. - /// - /// #### Example 2B - Propagating a N-API JS exception: - /// - /// Napi::Function jsFunctionThatThrows = someObj.As(); - /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); - /// if (result.IsEmpty()) return; - /// - /// An empty value result from a N-API call indicates an error occurred, and a JavaScript - /// exception is pending. To let the exception propagate, the code should generally return - /// immediately from the native callback, after performing any necessary cleanup. - /// - /// #### Example 3B - Handling a N-API JS exception: - /// - /// Napi::Function jsFunctionThatThrows = someObj.As(); - /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); - /// if (result.IsEmpty()) { - /// Napi::Error e = env.GetAndClearPendingException(); - /// cerr << "Caught JavaScript exception: " + e.Message(); - /// } - /// - /// Since the exception was cleared here, it will not be propagated as a JavaScript exception - /// after the native callback returns. - class Error : public ObjectReference -#ifdef NAPI_CPP_EXCEPTIONS - , public std::exception -#endif // NAPI_CPP_EXCEPTIONS - { - public: - static Error New(napi_env env); - static Error New(napi_env env, const char* message); - static Error New(napi_env env, const std::string& message); + public: + inline const_iterator& operator++(); - static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + inline bool operator==(const const_iterator& other) const; - Error(); - Error(napi_env env, napi_value value); + inline bool operator!=(const const_iterator& other) const; - // An error can be moved or copied. - Error(Error&& other); - Error& operator =(Error&& other); - Error(const Error&); - Error& operator =(const Error&); + inline const std::pair> operator*() + const; - const std::string& Message() const NAPI_NOEXCEPT; - void ThrowAsJavaScriptException() const; + private: + const Napi::Object* _object; + Array _keys; + uint32_t _index; -#ifdef NAPI_CPP_EXCEPTIONS - const char* what() const NAPI_NOEXCEPT override; -#endif // NAPI_CPP_EXCEPTIONS - - protected: - /// !cond INTERNAL - typedef napi_status (*create_error_fn)(napi_env envb, napi_value code, napi_value msg, napi_value* result); - - template - static TError New(napi_env env, - const char* message, - size_t length, - create_error_fn create_error); - /// !endcond - - private: - mutable std::string _message; - }; + friend class Object; +}; - class TypeError : public Error { - public: - static TypeError New(napi_env env, const char* message); - static TypeError New(napi_env env, const std::string& message); +class Object::iterator { + private: + enum class Type { BEGIN, END }; - TypeError(); - TypeError(napi_env env, napi_value value); - }; + inline iterator(Object* object, const Type type); - class RangeError : public Error { - public: - static RangeError New(napi_env env, const char* message); - static RangeError New(napi_env env, const std::string& message); + public: + inline iterator& operator++(); - RangeError(); - RangeError(napi_env env, napi_value value); - }; + inline bool operator==(const iterator& other) const; - class CallbackInfo { - public: - CallbackInfo(napi_env env, napi_callback_info info); - ~CallbackInfo(); + inline bool operator!=(const iterator& other) const; - // Disallow copying to prevent multiple free of _dynamicArgs - NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) + inline std::pair> operator*(); - Napi::Env Env() const; - Value NewTarget() const; - bool IsConstructCall() const; - size_t Length() const; - const Value operator [](size_t index) const; - Value This() const; - void* Data() const; - void SetData(void* data); - - private: - const size_t _staticArgCount = 6; - napi_env _env; - napi_callback_info _info; - napi_value _this; - size_t _argc; - napi_value* _argv; - napi_value _staticArgs[6]; - napi_value* _dynamicArgs; - void* _data; - }; + private: + Napi::Object* _object; + Array _keys; + uint32_t _index; - class PropertyDescriptor { - public: - typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); - typedef void (*SetterCallback)(const Napi::CallbackInfo& info); + friend class Object; +}; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS -#ifndef NODE_ADDON_API_DISABLE_DEPRECATED - template - static PropertyDescriptor Accessor(const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(napi_value name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(napi_value name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED - - template - static PropertyDescriptor Accessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(const std::string& utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(Name name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(const std::string& utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(Name name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Accessor(Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(Napi::Env env, - Napi::Object object, - const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor Function(Napi::Env env, - Napi::Object object, - Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor Value(const char* utf8name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value(const std::string& utf8name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value(napi_value name, - napi_value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor Value(Name name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - - PropertyDescriptor(napi_property_descriptor desc); - - operator napi_property_descriptor&(); - operator const napi_property_descriptor&() const; - - private: - napi_property_descriptor _desc; - }; +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +class SharedArrayBuffer : public Object { + public: + SharedArrayBuffer(); + SharedArrayBuffer(napi_env env, napi_value value); - /// Property descriptor for use with `ObjectWrap::DefineClass()`. - /// - /// This is different from the standalone `PropertyDescriptor` because it is specific to each - /// `ObjectWrap` subclass. This prevents using descriptors from a different class when - /// defining a new class (preventing the callbacks from having incorrect `this` pointers). - template - class ClassPropertyDescriptor { - public: - ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} + static SharedArrayBuffer New(napi_env env, size_t byteLength); - operator napi_property_descriptor&() { return _desc; } - operator const napi_property_descriptor&() const { return _desc; } + static void CheckCast(napi_env env, napi_value value); - private: - napi_property_descriptor _desc; - }; + void* Data(); + size_t ByteLength(); +}; +#endif - template - struct MethodCallbackData { - TCallback callback; - void* data; - }; +/// A JavaScript array buffer value. +class ArrayBuffer : public Object { + public: + /// Creates a new ArrayBuffer instance over a new automatically-allocated + /// buffer. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + size_t byteLength ///< Length of the buffer to be allocated, in bytes + ); + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength ///< Length of the external buffer to be used by the + ///< array, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env env, + /// void* externalData)` + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback, ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData, Hint* hint)` + Hint* finalizeHint ///< Hint (second parameter) to be passed to the + ///< finalize callback + ); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static void CheckCast(napi_env env, napi_value value); + + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. + ArrayBuffer(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + void* Data(); ///< Gets a pointer to the data buffer. + size_t ByteLength(); ///< Gets the length of the array buffer in bytes. - template - struct AccessorCallbackData { - TGetterCallback getterCallback; - TSetterCallback setterCallback; - void* data; - }; +#if NAPI_VERSION >= 7 + bool IsDetached() const; + void Detach(); +#endif // NAPI_VERSION >= 7 +}; - template - class InstanceWrap { - public: +/// A JavaScript typed-array value with unknown array type. +/// +/// For type-specific operations, cast to a `TypedArrayOf` instance using the +/// `As()` method: +/// +/// Napi::TypedArray array = ... +/// if (t.TypedArrayType() == napi_int32_array) { +/// Napi::Int32Array int32Array = t.As(); +/// } +class TypedArray : public Object { + public: + static void CheckCast(napi_env env, napi_value value); + + TypedArray(); ///< Creates a new _empty_ TypedArray instance. + TypedArray(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + napi_typedarray_type TypedArrayType() + const; ///< Gets the type of this typed-array. + + // Gets the backing `ArrayBuffer`. + // + // If this `TypedArray` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; + + uint8_t ElementSize() + const; ///< Gets the size in bytes of one element in the array. + size_t ElementLength() const; ///< Gets the number of elements in the array. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + protected: + /// !cond INTERNAL + napi_typedarray_type _type; + size_t _length; + + TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length); - typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info); - typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value); - - typedef ClassPropertyDescriptor PropertyDescriptor; - - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceValue(const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue(Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - - protected: - static void AttachPropData(napi_env env, napi_value value, const napi_property_descriptor* prop); + template + static +#if defined(NAPI_HAS_CONSTEXPR) + constexpr +#endif + napi_typedarray_type + TypedArrayTypeForPrimitiveType() { + return std::is_same::value ? napi_int8_array + : std::is_same::value ? napi_uint8_array + : std::is_same::value ? napi_int16_array + : std::is_same::value ? napi_uint16_array + : std::is_same::value ? napi_int32_array + : std::is_same::value ? napi_uint32_array + : std::is_same::value ? napi_float32_array + : std::is_same::value ? napi_float64_array +#if NAPI_VERSION > 5 + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array +#endif // NAPI_VERSION > 5 + : napi_int8_array; + } + /// !endcond +}; - private: - using This = InstanceWrap; +/// A JavaScript typed-array value with known array type. +/// +/// Note while it is possible to create and access Uint8 "clamped" arrays using +/// this class, the _clamping_ behavior is only applied in JavaScript. +template +class TypedArrayOf : public TypedArray { + public: + /// Creates a new TypedArray instance over a new automatically-allocated array + /// buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); - typedef MethodCallbackData InstanceVoidMethodCallbackData; - typedef MethodCallbackData InstanceMethodCallbackData; - typedef AccessorCallbackData InstanceAccessorCallbackData; + /// Creates a new TypedArray instance over a provided array buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); - static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + /// Creates a new TypedArray instance over a provided SharedArrayBuffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::SharedArrayBuffer + arrayBuffer, ///< Backing shared array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); +#endif - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + static void CheckCast(napi_env env, napi_value value); - template struct SetterTag {}; + TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. + TypedArrayOf(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. - template - static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } - }; + T& operator[](size_t index); ///< Gets or sets an element in the array. + const T& operator[](size_t index) const; ///< Gets an element in the array. - /// Base class to be extended by C++ classes exposed to JavaScript; each C++ class instance gets - /// "wrapped" by a JavaScript object that is managed by this class. - /// - /// At initialization time, the `DefineClass()` method must be used to - /// hook up the accessor and method callbacks. It takes a list of - /// property descriptors, which can be constructed via the various - /// static methods on the base class. - /// - /// #### Example: + /// Gets a pointer to the array's backing buffer. /// - /// class Example: public Napi::ObjectWrap { - /// public: - /// static void Initialize(Napi::Env& env, Napi::Object& target) { - /// Napi::Function constructor = DefineClass(env, "Example", { - /// InstanceAccessor<&Example::GetSomething, &Example::SetSomething>("value"), - /// InstanceMethod<&Example::DoSomething>("doSomething"), - /// }); - /// target.Set("Example", constructor); - /// } - /// - /// Example(const Napi::CallbackInfo& info); // Constructor - /// Napi::Value GetSomething(const Napi::CallbackInfo& info); - /// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& value); - /// Napi::Value DoSomething(const Napi::CallbackInfo& info); - /// } - template - class ObjectWrap : public InstanceWrap, public Reference { - public: - ObjectWrap(const CallbackInfo& callbackInfo); - virtual ~ObjectWrap(); - - static T* Unwrap(Object wrapper); - - // Methods exposed to JavaScript must conform to one of these callback signatures. - typedef void (*StaticVoidMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (*StaticMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (*StaticGetterCallback)(const CallbackInfo& info); - typedef void (*StaticSetterCallback)(const CallbackInfo& info, const Napi::Value& value); - - typedef ClassPropertyDescriptor PropertyDescriptor; - - static Function DefineClass(Napi::Env env, - const char* utf8name, - const std::initializer_list& properties, - void* data = nullptr); - static Function DefineClass(Napi::Env env, - const char* utf8name, - const std::vector& properties, - void* data = nullptr); - static PropertyDescriptor StaticMethod(const char* utf8name, - StaticVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod(const char* utf8name, - StaticMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod(Symbol name, - StaticVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticMethod(Symbol name, - StaticMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticAccessor(const char* utf8name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticAccessor(Symbol name, - StaticGetterCallback getter, - StaticSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticAccessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor StaticAccessor(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor StaticValue(const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor StaticValue(Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - virtual void Finalize(Napi::Env env); - - private: - using This = ObjectWrap; - - static napi_value ConstructorCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value StaticVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value StaticMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value StaticGetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value StaticSetterCallbackWrapper(napi_env env, napi_callback_info info); - static void FinalizeCallback(napi_env env, void* data, void* hint); - static Function DefineClass(Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* props, - void* data = nullptr); - - typedef MethodCallbackData StaticVoidMethodCallbackData; - typedef MethodCallbackData StaticMethodCallbackData; - - typedef AccessorCallbackData StaticAccessorCallbackData; - - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template struct StaticSetterTag {}; - - template - static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return nullptr; } - - bool _construction_failed = true; - }; + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + T* Data(); - class HandleScope { - public: - HandleScope(napi_env env, napi_handle_scope scope); - explicit HandleScope(Napi::Env env); - ~HandleScope(); + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + const T* Data() const; + + private: + T* _data; + + TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data); +}; + +/// The DataView provides a low-level interface for reading/writing multiple +/// number types in an ArrayBuffer irrespective of the platform's endianness. +class DataView : public Object { + public: + static DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + static DataView New(napi_env env, Napi::SharedArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); +#endif - // Disallow copying to prevent double close of napi_handle_scope - NAPI_DISALLOW_ASSIGN_COPY(HandleScope) + static void CheckCast(napi_env env, napi_value value); + + DataView(); ///< Creates a new _empty_ DataView instance. + DataView(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + // Gets the backing `ArrayBuffer`. + // + // If this `DataView` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + void* Data() const; + + float GetFloat32(size_t byteOffset) const; + double GetFloat64(size_t byteOffset) const; + int8_t GetInt8(size_t byteOffset) const; + int16_t GetInt16(size_t byteOffset) const; + int32_t GetInt32(size_t byteOffset) const; + uint8_t GetUint8(size_t byteOffset) const; + uint16_t GetUint16(size_t byteOffset) const; + uint32_t GetUint32(size_t byteOffset) const; + + void SetFloat32(size_t byteOffset, float value) const; + void SetFloat64(size_t byteOffset, double value) const; + void SetInt8(size_t byteOffset, int8_t value) const; + void SetInt16(size_t byteOffset, int16_t value) const; + void SetInt32(size_t byteOffset, int32_t value) const; + void SetUint8(size_t byteOffset, uint8_t value) const; + void SetUint16(size_t byteOffset, uint16_t value) const; + void SetUint32(size_t byteOffset, uint32_t value) const; + + private: + template + T ReadData(size_t byteOffset) const; - operator napi_handle_scope() const; + template + void WriteData(size_t byteOffset, T value) const; + + void* _data{}; + size_t _length{}; +}; + +class Function : public Object { + public: + using VoidCallback = void (*)(const CallbackInfo& info); + using Callback = Value (*)(const CallbackInfo& info); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const char* utf8name = nullptr, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data = nullptr); + + static void CheckCast(napi_env env, napi_value value); + + Function(); + Function(napi_env env, napi_value value); + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call(const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call(napi_value recv, + const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; +}; + +class Promise : public Object { + public: + class Deferred { + public: + static Deferred New(napi_env env); + Deferred(napi_env env); + Napi::Promise Promise() const; Napi::Env Env() const; - private: + void Resolve(napi_value value) const; + void Reject(napi_value value) const; + + private: napi_env _env; - napi_handle_scope _scope; + napi_deferred _deferred; + napi_value _promise; }; - class EscapableHandleScope { - public: - EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); - explicit EscapableHandleScope(Napi::Env env); - ~EscapableHandleScope(); + static void CheckCast(napi_env env, napi_value value); + + Promise(); + Promise(napi_env env, napi_value value); + + MaybeOrValue Then(napi_value onFulfilled) const; + MaybeOrValue Then(napi_value onFulfilled, + napi_value onRejected) const; + MaybeOrValue Catch(napi_value onRejected) const; + + MaybeOrValue Then(const Function& onFulfilled) const; + MaybeOrValue Then(const Function& onFulfilled, + const Function& onRejected) const; + MaybeOrValue Catch(const Function& onRejected) const; +}; + +template +class Buffer : public Uint8Array { + public: + static Buffer New(napi_env env, size_t length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + static Buffer New(napi_env env, T* data, size_t length); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static Buffer NewOrCopy(napi_env env, T* data, size_t length); + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); - // Disallow copying to prevent double close of napi_escapable_handle_scope - NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) + static Buffer Copy(napi_env env, const T* data, size_t length); - operator napi_escapable_handle_scope() const; + static void CheckCast(napi_env env, napi_value value); - Napi::Env Env() const; - Value Escape(napi_value escapee); + Buffer(); + Buffer(napi_env env, napi_value value); + size_t Length() const; + T* Data() const; - private: - napi_env _env; - napi_escapable_handle_scope _scope; - }; + private: +}; -#if (NAPI_VERSION > 2) - class CallbackScope { - public: - CallbackScope(napi_env env, napi_callback_scope scope); - CallbackScope(napi_env env, napi_async_context context); - virtual ~CallbackScope(); +/// Holds a counted reference to a value; initially a weak reference unless +/// otherwise specified, may be changed to/from a strong reference by adjusting +/// the refcount. +/// +/// The referenced value is not immediately destroyed when the reference count +/// is zero; it is merely then eligible for garbage-collection if there are no +/// other references to the value. +template +class Reference { + public: + static Reference New(const T& value, uint32_t initialRefcount = 0); + + Reference(); + Reference(napi_env env, napi_ref ref); + ~Reference(); + + // A reference can be moved but cannot be copied. + Reference(Reference&& other); + Reference& operator=(Reference&& other); + NAPI_DISALLOW_ASSIGN(Reference) + + operator napi_ref() const; + bool operator==(const Reference& other) const; + bool operator!=(const Reference& other) const; + + Napi::Env Env() const; + bool IsEmpty() const; + + // Note when getting the value of a Reference it is usually correct to do so + // within a HandleScope so that the value handle gets cleaned up efficiently. + T Value() const; + + uint32_t Ref() const; + uint32_t Unref() const; + void Reset(); + void Reset(const T& value, uint32_t refcount = 0); + + // Call this on a reference that is declared as static data, to prevent its + // destructor from running at program shutdown time, which would attempt to + // reset the reference when the environment is no longer valid. Avoid using + // this if at all possible. If you do need to use static data, MAKE SURE to + // warn your users that your addon is NOT threadsafe. + void SuppressDestruct(); + + protected: + Reference(const Reference&); + + /// !cond INTERNAL + napi_env _env; + napi_ref _ref; + /// !endcond + + private: + bool _suppressDestruct; +}; + +class ObjectReference : public Reference { + public: + ObjectReference(); + ObjectReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + ObjectReference(Reference&& other); + ObjectReference& operator=(Reference&& other); + ObjectReference(ObjectReference&& other); + ObjectReference& operator=(ObjectReference&& other); + NAPI_DISALLOW_ASSIGN(ObjectReference) + + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value) const; + MaybeOrValue Set(const char* utf8name, Napi::Value value) const; + MaybeOrValue Set(const char* utf8name, const char* utf8value) const; + MaybeOrValue Set(const char* utf8name, bool boolValue) const; + MaybeOrValue Set(const char* utf8name, double numberValue) const; + MaybeOrValue Set(const std::string& utf8name, napi_value value) const; + MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; + MaybeOrValue Set(const std::string& utf8name, + const std::string& utf8value) const; + MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; + MaybeOrValue Set(const std::string& utf8name, double numberValue) const; + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value) const; + MaybeOrValue Set(uint32_t index, const Napi::Value value) const; + MaybeOrValue Set(uint32_t index, const char* utf8value) const; + MaybeOrValue Set(uint32_t index, const std::string& utf8value) const; + MaybeOrValue Set(uint32_t index, bool boolValue) const; + MaybeOrValue Set(uint32_t index, double numberValue) const; + + protected: + ObjectReference(const ObjectReference&); +}; + +class FunctionReference : public Reference { + public: + FunctionReference(); + FunctionReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + FunctionReference(Reference&& other); + FunctionReference& operator=(Reference&& other); + FunctionReference(FunctionReference&& other); + FunctionReference& operator=(FunctionReference&& other); + NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; +}; + +// Shortcuts to creating a new reference with inferred type and refcount = 0. +template +Reference Weak(T value); +ObjectReference Weak(Object value); +FunctionReference Weak(Function value); + +// Shortcuts to creating a new reference with inferred type and refcount = 1. +template +Reference Persistent(T value); +ObjectReference Persistent(Object value); +FunctionReference Persistent(Function value); + +/// A persistent reference to a JavaScript error object. Use of this class +/// depends somewhat on whether C++ exceptions are enabled at compile time. +/// +/// ### Handling Errors With C++ Exceptions +/// +/// If C++ exceptions are enabled, then the `Error` class extends +/// `std::exception` and enables integrated error-handling for C++ exceptions +/// and JavaScript exceptions. +/// +/// If a Node-API call fails without executing any JavaScript code (for +/// example due to an invalid argument), then the Node-API wrapper +/// automatically converts and throws the error as a C++ exception of type +/// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API +/// throws a JavaScript exception, then the Node-API wrapper automatically +/// converts and throws it as a C++ exception of type `Napi::Error`. +/// +/// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ +/// callback, then the Node-API wrapper automatically converts and throws it +/// as a JavaScript exception. Therefore, catching a C++ exception of type +/// `Napi::Error` prevents a JavaScript exception from being thrown. +/// +/// #### Example 1A - Throwing a C++ exception: +/// +/// Napi::Env env = ... +/// throw Napi::Error::New(env, "Example exception"); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 2A - Propagating a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 3A - Handling a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result; +/// try { +/// result = jsFunctionThatThrows({ arg1, arg2 }); +/// } catch (const Napi::Error& e) { +/// cerr << "Caught JavaScript exception: " + e.what(); +/// } +/// +/// Since the exception was caught here, it will not be propagated as a +/// JavaScript exception. +/// +/// ### Handling Errors Without C++ Exceptions +/// +/// If C++ exceptions are disabled (by defining +/// `NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS`) then this class does not extend +/// `std::exception`, and APIs in the `Napi` namespace do not throw C++ +/// exceptions when they fail. Instead, they raise _pending_ JavaScript +/// exceptions and return _empty_ `Value`s. Calling code should check +/// `Value::IsEmpty()` before attempting to use a returned value, and may use +/// methods on the `Env` class to check for, get, and clear a pending JavaScript +/// exception. If the pending exception is not cleared, it will be thrown when +/// the native callback returns to JavaScript. +/// +/// #### Example 1B - Throwing a JS exception +/// +/// Napi::Env env = ... +/// Napi::Error::New(env, "Example +/// exception").ThrowAsJavaScriptException(); return; +/// +/// After throwing a JS exception, the code should generally return +/// immediately from the native callback, after performing any necessary +/// cleanup. +/// +/// #### Example 2B - Propagating a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) return; +/// +/// An empty value result from a Node-API call indicates an error occurred, +/// and a JavaScript exception is pending. To let the exception propagate, the +/// code should generally return immediately from the native callback, after +/// performing any necessary cleanup. +/// +/// #### Example 3B - Handling a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) { +/// Napi::Error e = env.GetAndClearPendingException(); +/// cerr << "Caught JavaScript exception: " + e.Message(); +/// } +/// +/// Since the exception was cleared here, it will not be propagated as a +/// JavaScript exception after the native callback returns. +class Error : public ObjectReference +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + , + public std::exception +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +{ + public: + static Error New(napi_env env); + static Error New(napi_env env, const char* message); + static Error New(napi_env env, const std::string& message); + + static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + + Error(); + Error(napi_env env, napi_value value); + + // An error can be moved or copied. + Error(Error&& other); + Error& operator=(Error&& other); + Error(const Error&); + Error& operator=(const Error&); + + const std::string& Message() const NAPI_NOEXCEPT; + void ThrowAsJavaScriptException() const; + + Object Value() const; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + const char* what() const NAPI_NOEXCEPT override; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + + protected: + /// !cond INTERNAL + using create_error_fn = napi_status (*)(napi_env envb, + napi_value code, + napi_value msg, + napi_value* result); + + template + static TError New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error); + /// !endcond + + private: + static inline const char* ERROR_WRAP_VALUE() NAPI_NOEXCEPT; + mutable std::string _message; +}; + +class TypeError : public Error { + public: + static TypeError New(napi_env env, const char* message); + static TypeError New(napi_env env, const std::string& message); + + TypeError(); + TypeError(napi_env env, napi_value value); +}; + +class RangeError : public Error { + public: + static RangeError New(napi_env env, const char* message); + static RangeError New(napi_env env, const std::string& message); + + RangeError(); + RangeError(napi_env env, napi_value value); +}; + +#if NAPI_VERSION > 8 +class SyntaxError : public Error { + public: + static SyntaxError New(napi_env env, const char* message); + static SyntaxError New(napi_env env, const std::string& message); + + SyntaxError(); + SyntaxError(napi_env env, napi_value value); +}; +#endif // NAPI_VERSION > 8 + +class CallbackInfo { + public: + CallbackInfo(napi_env env, napi_callback_info info); + ~CallbackInfo(); + + // Disallow copying to prevent multiple free of _dynamicArgs + NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) + + Napi::Env Env() const; + Value NewTarget() const; + bool IsConstructCall() const; + size_t Length() const; + const Value operator[](size_t index) const; + Value This() const; + void* Data() const; + void SetData(void* data); + explicit operator napi_callback_info() const; + + private: + const size_t _staticArgCount = 6; + napi_env _env; + napi_callback_info _info; + napi_value _this; + size_t _argc; + napi_value* _argv; + napi_value _staticArgs[6]{}; + napi_value* _dynamicArgs; + void* _data; +}; + +class PropertyDescriptor { + public: + using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); + using SetterCallback = void (*)(const Napi::CallbackInfo& info); + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + napi_value name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + Name name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + PropertyDescriptor(napi_property_descriptor desc); + + operator napi_property_descriptor&(); + operator const napi_property_descriptor&() const; + + private: + napi_property_descriptor _desc; +}; + +/// Property descriptor for use with `ObjectWrap::DefineClass()`. +/// +/// This is different from the standalone `PropertyDescriptor` because it is +/// specific to each `ObjectWrap` subclass. This prevents using descriptors +/// from a different class when defining a new class (preventing the callbacks +/// from having incorrect `this` pointers). +template +class ClassPropertyDescriptor { + public: + ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} + + operator napi_property_descriptor&() { return _desc; } + operator const napi_property_descriptor&() const { return _desc; } + + private: + napi_property_descriptor _desc; +}; + +template +struct MethodCallbackData { + TCallback callback; + void* data; +}; + +template +struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; +}; + +template +class InstanceWrap { + public: + using InstanceVoidMethodCallback = void (T::*)(const CallbackInfo& info); + using InstanceMethodCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + using InstanceVoidMethodCallbackData = + MethodCallbackData; + using InstanceMethodCallbackData = + MethodCallbackData; + using InstanceAccessorCallbackData = + AccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, + napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct SetterTag {}; + + template + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return nullptr; + } +}; + +/// Base class to be extended by C++ classes exposed to JavaScript; each C++ +/// class instance gets "wrapped" by a JavaScript object that is managed by this +/// class. +/// +/// At initialization time, the `DefineClass()` method must be used to +/// hook up the accessor and method callbacks. It takes a list of +/// property descriptors, which can be constructed via the various +/// static methods on the base class. +/// +/// #### Example: +/// +/// class Example: public Napi::ObjectWrap { +/// public: +/// static void Initialize(Napi::Env& env, Napi::Object& target) { +/// Napi::Function constructor = DefineClass(env, "Example", { +/// InstanceAccessor<&Example::GetSomething, +/// &Example::SetSomething>("value"), +/// InstanceMethod<&Example::DoSomething>("doSomething"), +/// }); +/// target.Set("Example", constructor); +/// } +/// +/// Example(const Napi::CallbackInfo& info); // Constructor +/// Napi::Value GetSomething(const Napi::CallbackInfo& info); +/// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& +/// value); Napi::Value DoSomething(const Napi::CallbackInfo& info); +/// } +template +class ObjectWrap : public InstanceWrap, public Reference { + public: + ObjectWrap(const CallbackInfo& callbackInfo); + virtual ~ObjectWrap(); + + static T* Unwrap(Object wrapper); + + // Methods exposed to JavaScript must conform to one of these callback + // signatures. + using StaticVoidMethodCallback = void (*)(const CallbackInfo& info); + using StaticMethodCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticGetterCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticSetterCallback = void (*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static Function DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list& properties, + void* data = nullptr); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const std::vector& properties, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); + virtual void Finalize(Napi::Env env); + virtual void Finalize(BasicEnv env); + + private: + using This = ObjectWrap; + + static napi_value ConstructorCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticSetterCallbackWrapper(napi_env env, + napi_callback_info info); + static void FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* hint); + + static void PostFinalizeCallback(napi_env env, void* data, void* hint); + + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); + + using StaticVoidMethodCallbackData = + MethodCallbackData; + using StaticMethodCallbackData = MethodCallbackData; + + using StaticAccessorCallbackData = + AccessorCallbackData; + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct StaticSetterTag {}; + + template + static napi_callback WrapStaticSetter(StaticSetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapStaticSetter(StaticSetterTag) + NAPI_NOEXCEPT { + return nullptr; + } - // Disallow copying to prevent double close of napi_callback_scope - NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) + bool _construction_failed = true; + bool _finalized = false; +}; - operator napi_callback_scope() const; +class HandleScope { + public: + HandleScope(napi_env env, napi_handle_scope scope); + explicit HandleScope(Napi::Env env); + ~HandleScope(); - Napi::Env Env() const; + // Disallow copying to prevent double close of napi_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(HandleScope) - private: - napi_env _env; - napi_callback_scope _scope; - }; -#endif + operator napi_handle_scope() const; - class AsyncContext { - public: - explicit AsyncContext(napi_env env, const char* resource_name); - explicit AsyncContext(napi_env env, const char* resource_name, const Object& resource); - virtual ~AsyncContext(); + Napi::Env Env() const; - AsyncContext(AsyncContext&& other); - AsyncContext& operator =(AsyncContext&& other); - NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) + private: + napi_env _env; + napi_handle_scope _scope; +}; - operator napi_async_context() const; +class EscapableHandleScope { + public: + EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); + explicit EscapableHandleScope(Napi::Env env); + ~EscapableHandleScope(); - Napi::Env Env() const; + // Disallow copying to prevent double close of napi_escapable_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) - private: - napi_env _env; - napi_async_context _context; - }; + operator napi_escapable_handle_scope() const; + + Napi::Env Env() const; + Value Escape(napi_value escapee); - class AsyncWorker { - public: - virtual ~AsyncWorker(); + private: + napi_env _env; + napi_escapable_handle_scope _scope; +}; - // An async worker can be moved but cannot be copied. - AsyncWorker(AsyncWorker&& other); - AsyncWorker& operator =(AsyncWorker&& other); - NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) +#if (NAPI_VERSION > 2) +class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); - operator napi_async_work() const; + // Disallow copying to prevent double close of napi_callback_scope + NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) - Napi::Env Env() const; + operator napi_callback_scope() const; - void Queue(); - void Cancel(); - void SuppressDestruct(); - - ObjectReference& Receiver(); - FunctionReference& Callback(); - - virtual void OnExecute(Napi::Env env); - virtual void OnWorkComplete(Napi::Env env, - napi_status status); - - protected: - explicit AsyncWorker(const Function& callback); - explicit AsyncWorker(const Function& callback, - const char* resource_name); - explicit AsyncWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncWorker(const Object& receiver, - const Function& callback); - explicit AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - - explicit AsyncWorker(Napi::Env env); - explicit AsyncWorker(Napi::Env env, - const char* resource_name); - explicit AsyncWorker(Napi::Env env, - const char* resource_name, - const Object& resource); - - virtual void Execute() = 0; - virtual void OnOK(); - virtual void OnError(const Error& e); - virtual void Destroy(); - virtual std::vector GetResult(Napi::Env env); - - void SetError(const std::string& error); - - private: - static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); - static inline void OnAsyncWorkComplete(napi_env env, - napi_status status, - void* asyncworker); + Napi::Env Env() const; - napi_env _env; - napi_async_work _work; - ObjectReference _receiver; - FunctionReference _callback; - std::string _error; - bool _suppress_destruct; - }; + private: + napi_env _env; + napi_callback_scope _scope; +}; +#endif - #if (NAPI_VERSION > 3 && !defined(__wasm32__)) - class ThreadSafeFunction { - public: - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - Finalizer finalizeCallback, - FinalizerDataType* data); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback); - - // This API may only be called from the main thread. - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data); - - ThreadSafeFunction(); - ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); - - operator napi_threadsafe_function() const; - - // This API may be called from any thread. - napi_status BlockingCall() const; - - // This API may be called from any thread. - template - napi_status BlockingCall(Callback callback) const; - - // This API may be called from any thread. - template - napi_status BlockingCall(DataType* data, Callback callback) const; - - // This API may be called from any thread. - napi_status NonBlockingCall() const; - - // This API may be called from any thread. - template - napi_status NonBlockingCall(Callback callback) const; - - // This API may be called from any thread. - template - napi_status NonBlockingCall(DataType* data, Callback callback) const; - - // This API may only be called from the main thread. - void Ref(napi_env env) const; - - // This API may only be called from the main thread. - void Unref(napi_env env) const; - - // This API may be called from any thread. - napi_status Acquire() const; - - // This API may be called from any thread. - napi_status Release(); - - // This API may be called from any thread. - napi_status Abort(); - - struct ConvertibleContext - { - template - operator T*() { return static_cast(context); } - void* context; - }; - - // This API may be called from any thread. - ConvertibleContext GetContext() const; - - private: - using CallbackWrapper = std::function; - - template - static ThreadSafeFunction New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper); - - napi_status CallInternal(CallbackWrapper* callbackWrapper, - napi_threadsafe_function_call_mode mode) const; - - static void CallJS(napi_env env, - napi_value jsCallback, - void* context, - void* data); - - napi_threadsafe_function _tsfn; +class AsyncContext { + public: + explicit AsyncContext(napi_env env, const char* resource_name); + explicit AsyncContext(napi_env env, + const char* resource_name, + const Object& resource); + virtual ~AsyncContext(); + + AsyncContext(AsyncContext&& other); + AsyncContext& operator=(AsyncContext&& other); + NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) + + operator napi_async_context() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _context; +}; + +#if NAPI_HAS_THREADS +class AsyncWorker { + public: + virtual ~AsyncWorker(); + + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) + + operator napi_async_work() const; + + Napi::Env Env() const; + + void Queue(); + void Cancel(); + void SuppressDestruct(); + + ObjectReference& Receiver(); + FunctionReference& Callback(); + + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, napi_status status); + + protected: + explicit AsyncWorker(const Function& callback); + explicit AsyncWorker(const Function& callback, const char* resource_name); + explicit AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncWorker(const Object& receiver, const Function& callback); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + + virtual void Execute() = 0; + virtual void OnOK(); + virtual void OnError(const Error& e); + virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); + + void SetError(const std::string& error); + + private: + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); + + napi_env _env; + napi_async_work _work; + ObjectReference _receiver; + FunctionReference _callback; + std::string _error; + bool _suppress_destruct; +}; +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +class ThreadSafeFunction { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + ThreadSafeFunction(); + ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data, Callback callback) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + struct ConvertibleContext { + template + operator T*() { + return static_cast(context); + } + void* context; }; - // A TypedThreadSafeFunction by default has no context (nullptr) and can - // accept any type (void) to its CallJs. - template - class TypedThreadSafeFunction { - public: - // This API may only be called from the main thread. - // Helper function that returns nullptr if running N-API 5+, otherwise a - // non-empty, no-op Function. This provides the ability to specify at - // compile-time a callback parameter to `New` that safely does no action - // when targeting _any_ N-API version. + // This API may be called from any thread. + ConvertibleContext GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + napi_status CallInternal(CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const; + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + napi_threadsafe_function _tsfn; +}; + +// A TypedThreadSafeFunction by default has no context (nullptr) and can +// accept any type (void) to its CallJs. +template +class TypedThreadSafeFunction { + public: + // This API may only be called from the main thread. + // Helper function that returns nullptr if running Node-API 5+, otherwise a + // non-empty, no-op Function. This provides the ability to specify at + // compile-time a callback parameter to `New` that safely does no action + // when targeting _any_ Node-API version. #if NAPI_VERSION > 4 - static std::nullptr_t EmptyFunctionFactory(Napi::Env env); + static std::nullptr_t EmptyFunctionFactory(Napi::Env env); #else - static Napi::Function EmptyFunctionFactory(Napi::Env env); + static Napi::Function EmptyFunctionFactory(Napi::Env env); #endif - static Napi::Function FunctionOrEmpty(Napi::Env env, - Napi::Function& callback); + static Napi::Function FunctionOrEmpty(Napi::Env env, + Napi::Function& callback); #if NAPI_VERSION > 4 - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [missing] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [passed] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [missing] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [missing] Resource [passed] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); #endif - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [missing] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [passed] Finalizer [missing] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [missing] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [passed] Resource [passed] Finalizer [passed] - template - static TypedThreadSafeFunction New( - napi_env env, - CallbackType callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); - - TypedThreadSafeFunction(); - TypedThreadSafeFunction( - napi_threadsafe_function tsFunctionValue); - - operator napi_threadsafe_function() const; - - // This API may be called from any thread. - napi_status BlockingCall(DataType* data = nullptr) const; - - // This API may be called from any thread. - napi_status NonBlockingCall(DataType* data = nullptr) const; - - // This API may only be called from the main thread. - void Ref(napi_env env) const; - - // This API may only be called from the main thread. - void Unref(napi_env env) const; - - // This API may be called from any thread. - napi_status Acquire() const; - - // This API may be called from any thread. - napi_status Release(); - - // This API may be called from any thread. - napi_status Abort(); - - // This API may be called from any thread. - ContextType* GetContext() const; + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + TypedThreadSafeFunction(); + TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall(DataType* data = nullptr) const; + + // This API may be called from any thread. + napi_status NonBlockingCall(DataType* data = nullptr) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + // This API may be called from any thread. + ContextType* GetContext() const; + + private: + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + static void CallJsInternal(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + protected: + napi_threadsafe_function _tsfn; +}; +template +class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { + return _asyncprogressworker; + } + DataType* data() { return _data; } private: - template - static TypedThreadSafeFunction New( - napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper); - - static void CallJsInternal(napi_env env, - napi_value jsCallback, - void* context, - void* data); - - protected: - napi_threadsafe_function _tsfn; + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; }; - template - class AsyncProgressWorkerBase : public AsyncWorker { - public: - virtual void OnWorkProgress(DataType* data) = 0; - class ThreadSafeData { - public: - ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) - : _asyncprogressworker(asyncprogressworker), _data(data) {} - - AsyncProgressWorkerBase* asyncprogressworker() { return _asyncprogressworker; }; - DataType* data() { return _data; }; - - private: - AsyncProgressWorkerBase* _asyncprogressworker; - DataType* _data; - }; - void OnWorkComplete(Napi::Env env, napi_status status) override; - protected: - explicit AsyncProgressWorkerBase(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource, - size_t queue_size = 1); - virtual ~AsyncProgressWorkerBase(); - -// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. -// Refs: https://github.com/nodejs/node/pull/27791 + void OnWorkComplete(Napi::Env env, napi_status status) override; + + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 #if NAPI_VERSION > 4 - explicit AsyncProgressWorkerBase(Napi::Env env, - const char* resource_name, - const Object& resource, - size_t queue_size = 1); + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); #endif - static inline void OnAsyncWorkProgress(Napi::Env env, - Napi::Function jsCallback, - void* data); + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); - napi_status NonBlockingCall(DataType* data); + napi_status NonBlockingCall(DataType* data); - private: - ThreadSafeFunction _tsfn; - bool _work_completed = false; - napi_status _complete_status; - static inline void OnThreadSafeFunctionFinalize(Napi::Env env, void* data, AsyncProgressWorkerBase* context); - }; + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize( + Napi::Env env, void* data, AsyncProgressWorkerBase* context); +}; - template - class AsyncProgressWorker : public AsyncProgressWorkerBase { - public: - virtual ~AsyncProgressWorker(); - - class ExecutionProgress { - friend class AsyncProgressWorker; - public: - void Signal() const; - void Send(const T* data, size_t count) const; - private: - explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} - AsyncProgressWorker* const _worker; - }; - - void OnWorkProgress(void*) override; - - protected: - explicit AsyncProgressWorker(const Function& callback); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - -// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. -// Refs: https://github.com/nodejs/node/pull/27791 -#if NAPI_VERSION > 4 - explicit AsyncProgressWorker(Napi::Env env); - explicit AsyncProgressWorker(Napi::Env env, - const char* resource_name); - explicit AsyncProgressWorker(Napi::Env env, - const char* resource_name, - const Object& resource); -#endif - virtual void Execute(const ExecutionProgress& progress) = 0; - virtual void OnProgress(const T* data, size_t count) = 0; +template +class AsyncProgressWorker : public AsyncProgressWorkerBase { + public: + virtual ~AsyncProgressWorker(); - private: - void Execute() override; - void Signal() const; - void SendProgress_(const T* data, size_t count); + class ExecutionProgress { + friend class AsyncProgressWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; - std::mutex _mutex; - T* _asyncdata; - size_t _asyncsize; + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; }; - template - class AsyncProgressQueueWorker : public AsyncProgressWorkerBase> { - public: - virtual ~AsyncProgressQueueWorker() {}; - - class ExecutionProgress { - friend class AsyncProgressQueueWorker; - public: - void Signal() const; - void Send(const T* data, size_t count) const; - private: - explicit ExecutionProgress(AsyncProgressQueueWorker* worker) : _worker(worker) {} - AsyncProgressQueueWorker* const _worker; - }; - - void OnWorkComplete(Napi::Env env, napi_status status) override; - void OnWorkProgress(std::pair*) override; - - protected: - explicit AsyncProgressQueueWorker(const Function& callback); - explicit AsyncProgressQueueWorker(const Function& callback, - const char* resource_name); - explicit AsyncProgressQueueWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncProgressQueueWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); - -// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. -// Refs: https://github.com/nodejs/node/pull/27791 + void OnWorkProgress(void*) override; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 #if NAPI_VERSION > 4 - explicit AsyncProgressQueueWorker(Napi::Env env); - explicit AsyncProgressQueueWorker(Napi::Env env, - const char* resource_name); - explicit AsyncProgressQueueWorker(Napi::Env env, - const char* resource_name, - const Object& resource); + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); #endif - virtual void Execute(const ExecutionProgress& progress) = 0; - virtual void OnProgress(const T* data, size_t count) = 0; + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; - private: - void Execute() override; - void Signal() const; - void SendProgress_(const T* data, size_t count); - }; - #endif // NAPI_VERSION > 3 && !defined(__wasm32__) + private: + void Execute() override; + void Signal(); + void SendProgress_(const T* data, size_t count); - // Memory management. - class MemoryManagement { - public: - static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); - }; + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + bool _signaled; +}; - // Version management - class VersionManagement { - public: - static uint32_t GetNapiVersion(Env env); - static const napi_node_version* GetNodeVersion(Env env); - }; +template +class AsyncProgressQueueWorker + : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker(){}; -#if NAPI_VERSION > 5 - template - class Addon : public InstanceWrap { - public: - static inline Object Init(Env env, Object exports); - static T* Unwrap(Object wrapper); + class ExecutionProgress { + friend class AsyncProgressQueueWorker; - protected: - typedef ClassPropertyDescriptor AddonProp; - void DefineAddon(Object exports, - const std::initializer_list& props); - Napi::Object DefineProperties(Object object, - const std::initializer_list& props); + public: + void Signal() const; + void Send(const T* data, size_t count) const; private: - Object entry_point_; + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) + : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); +}; +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +// Memory management. +class MemoryManagement { + public: + static int64_t AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes); +}; + +// Version management +class VersionManagement { + public: + static uint32_t GetNapiVersion(BasicEnv env); + static const napi_node_version* GetNodeVersion(BasicEnv env); +}; + +#if NAPI_VERSION > 5 +template +class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + using AddonProp = ClassPropertyDescriptor; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; +}; #endif // NAPI_VERSION > 5 -} // namespace Napi +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi // Inline implementations of all the above class methods are included here. #include "napi-inl.h" -#endif // SRC_NAPI_H_ +#endif // SRC_NAPI_H_ diff --git a/node_addon_api.gyp b/node_addon_api.gyp new file mode 100644 index 000000000..8c099262a --- /dev/null +++ b/node_addon_api.gyp @@ -0,0 +1,42 @@ +{ + 'targets': [ + { + 'target_name': 'node_addon_api', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['noexcept.gypi'], + } + }, + { + 'target_name': 'node_addon_api_except', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + } + }, + { + 'target_name': 'node_addon_api_except_all', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + 'defines': [ 'NODE_ADDON_API_CPP_EXCEPTIONS_ALL' ] + } + }, + { + 'target_name': 'node_addon_api_maybe', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['noexcept.gypi'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + } + }, + ] +} diff --git a/noexcept.gypi b/noexcept.gypi index 179f21f79..83df4ddf0 100644 --- a/noexcept.gypi +++ b/noexcept.gypi @@ -1,16 +1,26 @@ { - 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], + 'defines': [ 'NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS' ], 'cflags': [ '-fno-exceptions' ], 'cflags_cc': [ '-fno-exceptions' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'ExceptionHandling': 0, - 'EnablePREfast': 'true', - }, - }, - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', - }, + 'conditions': [ + ["OS=='win'", { + # _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi + #"defines": [ + # "_HAS_EXCEPTIONS=0" + #], + "msvs_settings": { + "VCCLCompilerTool": { + 'ExceptionHandling': 0, + 'EnablePREfast': 'true', + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', + }, + }], + ], } diff --git a/package.json b/package.json index 192c06f37..4504376cf 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,14 @@ "name": "Alba Mendez", "url": "https://github.com/jmendeth" }, + { + "name": "Alexander Floh", + "url": "https://github.com/alexanderfloh" + }, + { + "name": "Ammar Faizi", + "url": "https://github.com/ammarfaizi2" + }, { "name": "András Timár, Dr", "url": "https://github.com/timarandras" @@ -67,6 +75,10 @@ "name": "Daniel Bevenius", "url": "https://github.com/danbev" }, + { + "name": "Dante Calderón", + "url": "https://github.com/dantehemerson" + }, { "name": "Darshan Sen", "url": "https://github.com/RaisinTen" @@ -75,6 +87,10 @@ "name": "David Halls", "url": "https://github.com/davedoesdev" }, + { + "name": "Deepak Rajamohan", + "url": "https://github.com/deepakrkris" + }, { "name": "Dmitry Ashkadov", "url": "https://github.com/dmitryash" @@ -84,13 +100,25 @@ "url": "https://github.com/nadongguri" }, { - "name": "Ferdinand Holzer", - "url": "https://github.com/fholzer" + "name": "Doni Rubiagatra", + "url": "https://github.com/rubiagatra" }, { "name": "Eric Bickle", "url": "https://github.com/ebickle" }, + { + "name": "extremeheat", + "url": "https://github.com/extremeheat" + }, + { + "name": "Feng Yu", + "url": "https://github.com/F3n67u" + }, + { + "name": "Ferdinand Holzer", + "url": "https://github.com/fholzer" + }, { "name": "Gabriel Schulhof", "url": "https://github.com/gabrielschulhof" @@ -115,6 +143,10 @@ "name": "ikokostya", "url": "https://github.com/ikokostya" }, + { + "name": "Jack Xia", + "url": "https://github.com/JckXia" + }, { "name": "Jake Barnes", "url": "https://github.com/DuBistKomisch" @@ -127,6 +159,10 @@ "name": "Jason Ginchereau", "url": "https://github.com/jasongin" }, + { + "name": "Jenny", + "url": "https://github.com/egg-bread" + }, { "name": "Jeroen Janssen", "url": "https://github.com/japj" @@ -139,10 +175,18 @@ "name": "Jinho Bang", "url": "https://github.com/romandev" }, + { + "name": "José Expósito", + "url": "https://github.com/JoseExposito" + }, { "name": "joshgarde", "url": "https://github.com/joshgarde" }, + { + "name": "Julian Mesa", + "url": "https://github.com/julianmesa-gitkraken" + }, { "name": "Kasumi Hanazuki", "url": "https://github.com/hanazuki" @@ -155,6 +199,10 @@ "name": "Kevin Eady", "url": "https://github.com/KevinEady" }, + { + "name": "Kévin VOYER", + "url": "https://github.com/kecsou" + }, { "name": "kidneysolo", "url": "https://github.com/kidneysolo" @@ -171,10 +219,18 @@ "name": "Kyle Farnung", "url": "https://github.com/kfarnung" }, + { + "name": "Kyle Kovacs", + "url": "https://github.com/nullromo" + }, { "name": "legendecas", "url": "https://github.com/legendecas" }, + { + "name": "LongYinan", + "url": "https://github.com/Brooooooklyn" + }, { "name": "Lovell Fuller", "url": "https://github.com/lovell" @@ -191,6 +247,10 @@ "name": "Mathias Küsel", "url": "https://github.com/mathiask88" }, + { + "name": "Mathias Stearn", + "url": "https://github.com/RedBeard0531" + }, { "name": "Matteo Collina", "url": "https://github.com/mcollina" @@ -235,10 +295,22 @@ "name": "pacop", "url": "https://github.com/pacop" }, + { + "name": "Peter Šándor", + "url": "https://github.com/petersandor" + }, { "name": "Philipp Renoth", "url": "https://github.com/DaAitch" }, + { + "name": "rgerd", + "url": "https://github.com/rgerd" + }, + { + "name": "Richard Lau", + "url": "https://github.com/richardlau" + }, { "name": "Rolf Timmermans", "url": "https://github.com/rolftimmermans" @@ -251,6 +323,10 @@ "name": "Ryuichi Okumura", "url": "https://github.com/okuryu" }, + { + "name": "Saint Gabriel", + "url": "https://github.com/chineduG" + }, { "name": "Sampson Gao", "url": "https://github.com/sampsongao" @@ -259,6 +335,10 @@ "name": "Sam Roberts", "url": "https://github.com/sam-github" }, + { + "name": "strager", + "url": "https://github.com/strager" + }, { "name": "Taylor Woll", "url": "https://github.com/boingoing" @@ -275,6 +355,14 @@ "name": "Tobias Nießen", "url": "https://github.com/tniessen" }, + { + "name": "todoroff", + "url": "https://github.com/todoroff" + }, + { + "name": "Toyo Li", + "url": "https://github.com/toyobayashi" + }, { "name": "Tux3", "url": "https://github.com/tux3" @@ -283,6 +371,18 @@ "name": "Vlad Velmisov", "url": "https://github.com/Velmisov" }, + { + "name": "Vladimir Morozov", + "url": "https://github.com/vmoroz" + }, + { + "name": "WenheLI", + "url": "https://github.com/WenheLI" + }, + { + "name": "Xuguang Mei", + "url": "https://github.com/meixg" + }, { "name": "Yohei Kishimoto", "url": "https://github.com/morokosi" @@ -294,17 +394,39 @@ { "name": "Ziqiu Zhao", "url": "https://github.com/ZzqiZQute" + }, + { + "name": "Feng Yu", + "url": "https://github.com/F3n67u" + }, + { + "name": "wanlu wang", + "url": "https://github.com/wanlu" + }, + { + "name": "Caleb Hearon", + "url": "https://github.com/chearon" + }, + { + "name": "Marx", + "url": "https://github.com/MarxJiao" + }, + { + "name": "Ömer AKGÜL", + "url": "https://github.com/tuhalf" } ], - "dependencies": {}, - "description": "Node.js API (N-API)", + "description": "Node.js API (Node-API)", "devDependencies": { "benchmark": "^2.1.4", "bindings": "^1.5.0", "clang-format": "^1.4.0", - "fs-extra": "^9.0.1", + "eslint": "^9.13.0", + "fs-extra": "^11.1.1", + "neostandard": "^0.12.0", + "node-gyp": "^12.4.0", "pre-commit": "^1.2.2", - "safe-buffer": "^5.1.1" + "semver": "^7.6.0" }, "directories": {}, "gypfile": false, @@ -323,26 +445,37 @@ "license": "MIT", "main": "index.js", "name": "node-addon-api", - "optionalDependencies": {}, "readme": "README.md", "repository": { "type": "git", "url": "git://github.com/nodejs/node-addon-api.git" }, + "files": [ + "*.{c,h,gyp,gypi}", + "package-support.json", + "tools/" + ], "scripts": { "prebenchmark": "node-gyp rebuild -C benchmark", "benchmark": "node benchmark", + "create-coverage": "npm test --coverage", + "report-coverage-html": "rm -rf coverage-html && mkdir coverage-html && gcovr -e test --merge-mode-functions merge-use-line-max --html-nested ./coverage-html/index.html test", + "report-coverage-xml": "rm -rf coverage-xml && mkdir coverage-xml && gcovr -e test --merge-mode-functions merge-use-line-max --xml -o ./coverage-xml/coverage-cxx.xml test", "pretest": "node-gyp rebuild -C test", "test": "node test", + "test:debug": "node-gyp rebuild -C test --debug && NODE_API_BUILD_CONFIG=Debug node ./test/index.js", "predev": "node-gyp rebuild -C test --debug", "dev": "node test", "predev:incremental": "node-gyp configure build -C test --debug", "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile", - "lint": "node tools/clang-format.js", - "lint:fix": "git-clang-format '*.h', '*.cc'" + "lint": "eslint && node tools/clang-format", + "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "3.1.0", - "support": true + "version": "8.9.1", + "support": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } } diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..63ce659f2 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "node", + "pull-request-title-pattern": "chore: release v${version}", + "bootstrap-sha": "bc5acef9dd5298cbbcabd5c01c9590ada683951d", + "packages": { + ".": { + "include-component-in-tag": false, + "extra-files": [ + "README.md" + ], + "changelog-path": "CHANGELOG.md" + } + } +} diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..7ea20d765 --- /dev/null +++ b/test/README.md @@ -0,0 +1,91 @@ +# Writing Tests + +There are multiple flavors of node-addon-api test builds that cover different +build flags defined in `napi.h`: + +1. c++ exceptions enabled, +2. c++ exceptions disabled, +3. c++ exceptions disabled, and `NODE_ADDON_API_ENABLE_MAYBE` defined. + +Functions in node-addon-api that call into JavaScript can have different +declared return types to reflect build flavor settings. For example, +`Napi::Object::Set` returns `bool` when `NODE_ADDON_API_ENABLE_MAYBE` +is not defined, and `Napi::Maybe` when `NODE_ADDON_API_ENABLE_MAYBE` +is defined. In source code, return type variants are defined as +`Napi::MaybeOrValue<>` to prevent the duplication of most of the code base. + +To properly test these build flavors, all values returned by a function defined +to return `Napi::MaybeOrValue<>` should be tested by using one of the following +test helpers to handle possible JavaScript exceptions. + +There are three test helper functions to conveniently convert +`Napi::MaybeOrValue<>` values to raw values. + +## MaybeUnwrap + +```cpp +template +T MaybeUnwrap(MaybeOrValue maybe); +``` + +Converts `MaybeOrValue` to `T` by checking that `MaybeOrValue` is NOT an +empty `Maybe`. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Object obj = info[0].As(); +// we are sure the parameters should not throw +Value value = MaybeUnwrap(obj->Get("foobar")); +``` + +## MaybeUnwrapOr + +```cpp +template +T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value = T()); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +`Maybe` or return the `default_value` if the `Maybe` is empty. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Value CallWithArgs(const CallbackInfo& info) { + Function func = info[0].As(); + // We don't care if the operation is throwing or not, just return it back to node-addon-api + return MaybeUnwrapOr( + func.Call(std::initializer_list{info[1], info[2], info[3]})); +} +``` + +## MaybeUnwrapTo + +```cpp +template +bool MaybeUnwrapTo(MaybeOrValue maybe, T* out); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +e`Maybe` or return `false` if the Maybe is empty + +Copies the `value` to `out` when `NODE_ADDON_API_ENABLE_MAYBE` is not defined + +Example: + +```cpp +Object opts = info[0].As(); +bool hasProperty = false; +// The check may throw, but we are going to suppress that. +if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); +} else { + env.GetAndClearPendingException(); +} +``` diff --git a/test/addon.cc b/test/addon.cc index 9652f9aa4..1ec9343f0 100644 --- a/test/addon.cc +++ b/test/addon.cc @@ -7,14 +7,18 @@ namespace { class TestAddon : public Napi::Addon { public: inline TestAddon(Napi::Env env, Napi::Object exports) { - DefineAddon(exports, { - InstanceMethod("increment", &TestAddon::Increment), - InstanceValue("subObject", DefineProperties(Napi::Object::New(env), { - InstanceMethod("decrement", &TestAddon::Decrement) - })) - }); + DefineAddon( + exports, + {InstanceMethod("increment", &TestAddon::Increment), + InstanceValue( + "subObject", + DefineProperties( + Napi::Object::New(env), + {InstanceMethod("decrement", &TestAddon::Decrement)}))}); } + ~TestAddon() { fprintf(stderr, "TestAddon::~TestAddon\n"); } + private: Napi::Value Increment(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), ++value); @@ -27,10 +31,14 @@ class TestAddon : public Napi::Addon { uint32_t value = 42; }; +Napi::Value CreateAddon(const Napi::CallbackInfo& info) { + return TestAddon::Init(info.Env(), Napi::Object::New(info.Env())); +} + } // end of anonymous namespace Napi::Object InitAddon(Napi::Env env) { - return TestAddon::Init(env, Napi::Object::New(env)); + return Napi::Function::New(env, "CreateAddon"); } #endif // (NAPI_VERSION > 5) diff --git a/test/addon.js b/test/addon.js index 54a5f666a..c326b68e3 100644 --- a/test/addon.js +++ b/test/addon.js @@ -1,12 +1,7 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); - -function test(binding) { - assert.strictEqual(binding.addon.increment(), 43); - assert.strictEqual(binding.addon.increment(), 44); - assert.strictEqual(binding.addon.subObject.decrement(), 43); -} +module.exports = require('./common').runTestInChildProcess({ + suite: 'addon', + testName: 'workingCode', + expectedStderr: ['TestAddon::~TestAddon'] +}); diff --git a/test/addon_build/index.js b/test/addon_build/index.js index c410bf3a8..adbda28ce 100644 --- a/test/addon_build/index.js +++ b/test/addon_build/index.js @@ -4,28 +4,28 @@ const { promisify } = require('util'); const exec = promisify(require('child_process').exec); const { copy, remove } = require('fs-extra'); const path = require('path'); -const assert = require('assert') +const assert = require('assert'); const ADDONS_FOLDER = path.join(__dirname, 'addons'); const addons = [ 'echo addon', 'echo-addon' -] +]; -async function beforeAll(addons) { - console.log(' >Preparing native addons to build') +async function beforeAll (addons) { + console.log(' >Preparing native addons to build'); for (const addon of addons) { await remove(path.join(ADDONS_FOLDER, addon)); await copy(path.join(__dirname, 'tpl'), path.join(ADDONS_FOLDER, addon)); } } -async function test(addon) { +async function test (addon) { console.log(` >Building addon: '${addon}'`); - const { stderr, stdout } = await exec('npm install', { + await exec('npm install', { cwd: path.join(ADDONS_FOLDER, addon) - }) + }); console.log(` >Running test for: '${addon}'`); // Disabled the checks on stderr and stdout because of this issue on npm: // Stop using process.umask(): https://github.com/npm/cli/issues/1103 @@ -41,9 +41,9 @@ async function test(addon) { assert.strictEqual(binding.noexcept.echo(103), 103); } -module.exports = (async function() { +module.exports = (async function () { await beforeAll(addons); for (const addon of addons) { await test(addon); } -})() +})(); diff --git a/test/addon_build/tpl/addon.cc b/test/addon_build/tpl/addon.cc index 1a86799c4..32b000d79 100644 --- a/test/addon_build/tpl/addon.cc +++ b/test/addon_build/tpl/addon.cc @@ -3,7 +3,8 @@ Napi::Value Echo(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); if (info.Length() != 1) { - Napi::TypeError::New(env, "Wrong number of arguments. One argument expected.") + Napi::TypeError::New(env, + "Wrong number of arguments. One argument expected.") .ThrowAsJavaScriptException(); } return info[0].As(); diff --git a/test/addon_build/tpl/binding.gyp b/test/addon_build/tpl/binding.gyp index aa26f1acb..5b4f9f8ad 100644 --- a/test/addon_build/tpl/binding.gyp +++ b/test/addon_build/tpl/binding.gyp @@ -4,11 +4,12 @@ " 5) #include #include "napi.h" +#include "test_helper.h" // An overly elaborate way to get/set a boolean stored in the instance data: -// 0. A boolean named "verbose" is stored in the instance data. The constructor -// for JS `VerboseIndicator` instances is also stored in the instance data. +// 0. The constructor for JS `VerboseIndicator` instances, which have a private +// member named "verbose", is stored in the instance data. // 1. Add a property named "verbose" onto exports served by a getter/setter. -// 2. The getter returns a object of type VerboseIndicator, which itself has a +// 2. The getter returns an object of type VerboseIndicator, which itself has a // property named "verbose", also served by a getter/setter: // * The getter returns a boolean, indicating whether "verbose" is set. // * The setter sets "verbose" on the instance data. @@ -16,11 +17,10 @@ class Addon { public: class VerboseIndicator : public Napi::ObjectWrap { public: - VerboseIndicator(const Napi::CallbackInfo& info): - Napi::ObjectWrap(info) { - info.This().As()["verbose"] = - Napi::Boolean::New(info.Env(), - info.Env().GetInstanceData()->verbose); + VerboseIndicator(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + info.This().As()["verbose"] = Napi::Boolean::New( + info.Env(), info.Env().GetInstanceData()->verbose); } Napi::Value Getter(const Napi::CallbackInfo& info) { @@ -33,23 +33,24 @@ class Addon { } static Napi::FunctionReference Init(Napi::Env env) { - return Napi::Persistent(DefineClass(env, "VerboseIndicator", { - InstanceAccessor< - &VerboseIndicator::Getter, - &VerboseIndicator::Setter>("verbose") - })); + return Napi::Persistent(DefineClass( + env, + "VerboseIndicator", + {InstanceAccessor<&VerboseIndicator::Getter, + &VerboseIndicator::Setter>("verbose")})); } }; static Napi::Value Getter(const Napi::CallbackInfo& info) { - return info.Env().GetInstanceData()->VerboseIndicator.New({}); + return MaybeUnwrap( + info.Env().GetInstanceData()->VerboseIndicator.New({})); } static void Setter(const Napi::CallbackInfo& info) { info.Env().GetInstanceData()->verbose = info[0].As(); } - Addon(Napi::Env env): VerboseIndicator(VerboseIndicator::Init(env)) {} + Addon(Napi::Env env) : VerboseIndicator(VerboseIndicator::Init(env)) {} ~Addon() { if (verbose) { fprintf(stderr, "addon_data: Addon::~Addon\n"); @@ -58,7 +59,7 @@ class Addon { static void DeleteAddon(Napi::Env, Addon* addon, uint32_t* hint) { delete addon; - fprintf(stderr, "hint: %d\n", *hint); + fprintf(stderr, "hint: %u\n", *hint); delete hint; } @@ -74,7 +75,7 @@ class Addon { new uint32_t(hint)); Napi::Object result = Napi::Object::New(env); result.DefineProperties({ - Napi::PropertyDescriptor::Accessor("verbose"), + Napi::PropertyDescriptor::Accessor("verbose"), }); return result; diff --git a/test/addon_data.js b/test/addon_data.js index 571b23e72..900aa32c8 100644 --- a/test/addon_data.js +++ b/test/addon_data.js @@ -1,51 +1,24 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const { spawn } = require('child_process'); -const readline = require('readline'); -const path = require('path'); -module.exports = - test(path.resolve(__dirname, `./build/${buildType}/binding.node`)) - .then(() => - test(path.resolve(__dirname, - `./build/${buildType}/binding_noexcept.node`))); +const common = require('./common'); -// Make sure the instance data finalizer is called at process exit. If the hint -// is non-zero, it will be printed out by the child process. -function testFinalizer(bindingName, hint, expected) { - return new Promise((resolve) => { - bindingName = bindingName.split('\\').join('\\\\'); - const child = spawn(process.execPath, [ - '-e', - `require('${bindingName}').addon_data(${hint}).verbose = true;` - ]); - const actual = []; - readline - .createInterface({ input: child.stderr }) - .on('line', (line) => { - if (expected.indexOf(line) >= 0) { - actual.push(line); - } - }) - .on('close', () => { - assert.deepStrictEqual(expected, actual); - resolve(); - }); - }); -} +module.exports = common.runTest(test); -async function test(bindingName) { - const binding = require(bindingName).addon_data(0); +async function test () { + await common.runTestInChildProcess({ + suite: 'addon_data', + testName: 'workingCode' + }); - // Make sure it is possible to get/set instance data. - assert.strictEqual(binding.verbose.verbose, false); - binding.verbose = true; - assert.strictEqual(binding.verbose.verbose, true); - binding.verbose = false; - assert.strictEqual(binding.verbose.verbose, false); + await common.runTestInChildProcess({ + suite: 'addon_data', + testName: 'cleanupWithoutHint', + expectedStderr: ['addon_data: Addon::~Addon'] + }); - await testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']); - await testFinalizer(bindingName, 42, - ['addon_data: Addon::~Addon', 'hint: 42']); + await common.runTestInChildProcess({ + suite: 'addon_data', + testName: 'cleanupWithHint', + expectedStderr: ['addon_data: Addon::~Addon', 'hint: 42'] + }); } diff --git a/test/arraybuffer.cc b/test/array_buffer.cc similarity index 62% rename from test/arraybuffer.cc rename to test/array_buffer.cc index 4a1b2a34e..2b07bd250 100644 --- a/test/arraybuffer.cc +++ b/test/array_buffer.cc @@ -27,7 +27,8 @@ Value CreateBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } @@ -41,12 +42,14 @@ Value CreateExternalBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testData, testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != testData) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -60,21 +63,20 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { uint8_t* data = new uint8_t[testLength]; ArrayBuffer buffer = ArrayBuffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, void* finalizeData) { - delete[] static_cast(finalizeData); - finalizeCount++; - }); + info.Env(), data, testLength, [](Env /*env*/, void* finalizeData) { + delete[] static_cast(finalizeData); + finalizeCount++; + }); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -89,22 +91,24 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { char* hint = nullptr; ArrayBuffer buffer = ArrayBuffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) { - delete[] static_cast(finalizeData); - finalizeCount++; - }, - hint); + info.Env(), + data, + testLength, + [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) { + delete[] static_cast(finalizeData); + finalizeCount++; + }, + hint); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -114,31 +118,35 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { void CheckBuffer(const CallbackInfo& info) { if (!info[0].IsArrayBuffer()) { - Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A buffer was expected.") + .ThrowAsJavaScriptException(); return; } ArrayBuffer buffer = info[0].As(); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return; } if (!VerifyData(static_cast(buffer.Data()), testLength)) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return; } } Value GetFinalizeCount(const CallbackInfo& info) { - return Number::New(info.Env(), finalizeCount); + return Number::New(info.Env(), finalizeCount); } Value CreateBufferWithConstructor(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } InitData(static_cast(buffer.Data()), testLength); @@ -153,7 +161,8 @@ Value CheckEmptyBuffer(const CallbackInfo& info) { void CheckDetachUpdatesData(const CallbackInfo& info) { if (!info[0].IsArrayBuffer()) { - Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A buffer was expected.") + .ThrowAsJavaScriptException(); return; } @@ -165,7 +174,8 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { #if NAPI_VERSION >= 7 if (buffer.IsDetached()) { - Error::New(info.Env(), "Buffer should not be detached.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Buffer should not be detached.") + .ThrowAsJavaScriptException(); return; } #endif @@ -173,7 +183,8 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { if (info.Length() == 2) { // Detach externally (in JavaScript). if (!info[1].IsFunction()) { - Error::New(info.Env(), "A function was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A function was expected.") + .ThrowAsJavaScriptException(); return; } @@ -190,23 +201,26 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { #if NAPI_VERSION >= 7 if (!buffer.IsDetached()) { - Error::New(info.Env(), "Buffer should be detached.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Buffer should be detached.") + .ThrowAsJavaScriptException(); return; } #endif if (buffer.Data() != nullptr) { - Error::New(info.Env(), "Incorrect data pointer.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect data pointer.") + .ThrowAsJavaScriptException(); return; } if (buffer.ByteLength() != 0) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return; } } -} // end anonymous namespace +} // end anonymous namespace Object InitArrayBuffer(Env env) { Object exports = Object::New(env); @@ -214,14 +228,16 @@ Object InitArrayBuffer(Env env) { exports["createBuffer"] = Function::New(env, CreateBuffer); exports["createExternalBuffer"] = Function::New(env, CreateExternalBuffer); exports["createExternalBufferWithFinalize"] = - Function::New(env, CreateExternalBufferWithFinalize); + Function::New(env, CreateExternalBufferWithFinalize); exports["createExternalBufferWithFinalizeHint"] = - Function::New(env, CreateExternalBufferWithFinalizeHint); + Function::New(env, CreateExternalBufferWithFinalizeHint); exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); - exports["createBufferWithConstructor"] = Function::New(env, CreateBufferWithConstructor); + exports["createBufferWithConstructor"] = + Function::New(env, CreateBufferWithConstructor); exports["checkEmptyBuffer"] = Function::New(env, CheckEmptyBuffer); - exports["checkDetachUpdatesData"] = Function::New(env, CheckDetachUpdatesData); + exports["checkDetachUpdatesData"] = + Function::New(env, CheckDetachUpdatesData); return exports; } diff --git a/test/arraybuffer.js b/test/array_buffer.js similarity index 89% rename from test/arraybuffer.js rename to test/array_buffer.js index 363de17d9..08547ed7e 100644 --- a/test/arraybuffer.js +++ b/test/array_buffer.js @@ -1,12 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { return testUtil.runGCTests([ 'Internal ArrayBuffer', () => { @@ -59,12 +58,13 @@ function test(binding) { 'ArrayBuffer updates data pointer and length when detached', () => { // Detach the ArrayBuffer in JavaScript. + const mem = new WebAssembly.Memory({ initial: 1 }); binding.arraybuffer.checkDetachUpdatesData(mem.buffer, () => mem.grow(1)); // Let C++ detach the ArrayBuffer. const extBuffer = binding.arraybuffer.createExternalBuffer(); binding.arraybuffer.checkDetachUpdatesData(extBuffer); - }, + } ]); } diff --git a/test/async_context.cc b/test/async_context.cc new file mode 100644 index 000000000..be2e1d917 --- /dev/null +++ b/test/async_context.cc @@ -0,0 +1,36 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +static void MakeCallback(const CallbackInfo& info) { + Function callback = info[0].As(); + Object resource = info[1].As(); + AsyncContext context(info.Env(), "async_context_test", resource); + callback.MakeCallback( + Object::New(info.Env()), std::initializer_list{}, context); +} + +static void MakeCallbackNoResource(const CallbackInfo& info) { + Function callback = info[0].As(); + AsyncContext context(info.Env(), "async_context_no_res_test"); + callback.MakeCallback( + Object::New(info.Env()), std::initializer_list{}, context); +} + +static Boolean AssertAsyncContextReturnCorrectEnv(const CallbackInfo& info) { + AsyncContext context(info.Env(), "empty_context_test"); + return Boolean::New(info.Env(), context.Env() == info.Env()); +} +} // end anonymous namespace + +Object InitAsyncContext(Env env) { + Object exports = Object::New(env); + exports["makeCallback"] = Function::New(env, MakeCallback); + exports["makeCallbackNoResource"] = + Function::New(env, MakeCallbackNoResource); + exports["asyncCxtReturnCorrectEnv"] = + Function::New(env, AssertAsyncContextReturnCorrectEnv); + return exports; +} diff --git a/test/async_context.js b/test/async_context.js new file mode 100644 index 000000000..6cf0418f6 --- /dev/null +++ b/test/async_context.js @@ -0,0 +1,122 @@ +'use strict'; + +const assert = require('assert'); +const common = require('./common'); + +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0]; +let asyncHooks; +function checkAsyncHooks () { + if (nodeVersion >= 8) { + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); + } + return true; + } + return false; +} + +module.exports = common.runTest(test); + +function installAsyncHooksForTest (resName) { + return new Promise((resolve, reject) => { + let id; + const events = []; + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let destroyed = false; + const hook = asyncHooks.createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === resName) { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + destroyed = true; + } + } + }).enable(); + + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + }); +} + +async function makeCallbackWithResource (binding) { + const hooks = installAsyncHooksForTest('async_context_test'); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + binding.asynccontext.makeCallback(common.mustCall(), { foo: 'foo' }); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'async_context_test', + triggerAsyncId, + resource: { foo: 'foo' } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); +} + +async function makeCallbackWithoutResource (binding) { + const hooks = installAsyncHooksForTest('async_context_no_res_test'); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + binding.asynccontext.makeCallbackNoResource(common.mustCall()); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'async_context_no_res_test', + triggerAsyncId, + resource: { } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); +} + +function assertAsyncContextReturnsCorrectEnv (binding) { + assert.strictEqual(binding.asynccontext.asyncCxtReturnCorrectEnv(), true); +} + +async function test (binding) { + if (!checkAsyncHooks()) { + return; + } + + await makeCallbackWithResource(binding); + await makeCallbackWithoutResource(binding); + assertAsyncContextReturnsCorrectEnv(binding); +} diff --git a/test/async_progress_queue_worker.cc b/test/async_progress_queue_worker.cc new file mode 100644 index 000000000..90ff881ac --- /dev/null +++ b/test/async_progress_queue_worker.cc @@ -0,0 +1,248 @@ +#include "napi.h" + +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + int32_t progress; +}; + +class TestWorkerWithNoCb : public AsyncProgressQueueWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb); + worker->Queue(); + } break; + + case 2: { + std::string resName = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb); + worker->Queue(); + } break; + + case 3: { + std::string resName = info[0].As(); + Object resObject = info[1].As(); + Function cb = info[2].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), resObject, cb); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress& progress) override { + ProgressData data{1}; + progress.Send(&data, 1); + } + + void OnProgress(const ProgressData*, size_t /* count */) override { + _cb.Call({}); + } + + private: + TestWorkerWithNoCb(Napi::Env env, Function cb) + : AsyncProgressQueueWorker(env) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, const char* resourceName, Function cb) + : AsyncProgressQueueWorker(env, resourceName) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, + const char* resourceName, + const Object& resourceObject, + Function cb) + : AsyncProgressQueueWorker(env, resourceName, resourceObject) { + _cb.Reset(cb, 1); + } + FunctionReference _cb; +}; + +class TestWorkerWithRecv : public AsyncProgressQueueWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 2: { + Object recv = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb); + worker->Queue(); + } break; + + case 3: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str()); + worker->Queue(); + } break; + + case 4: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + Object resObject = info[3].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str(), resObject); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithRecv(const Object& recv, const Function& cb) + : AsyncProgressQueueWorker(recv, cb) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName) + : AsyncProgressQueueWorker(recv, cb, resourceName) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName, + const Object& resourceObject) + : AsyncProgressQueueWorker(recv, cb, resourceName, resourceObject) {} +}; + +class TestWorkerWithCb : public AsyncProgressQueueWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithCb* worker = new TestWorkerWithCb(cb); + worker->Queue(); + } break; + + case 2: { + Function cb = info[0].As(); + std::string asyncResName = info[1].As(); + TestWorkerWithCb* worker = + new TestWorkerWithCb(cb, asyncResName.c_str()); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithCb(Function cb) : AsyncProgressQueueWorker(cb) {} + TestWorkerWithCb(Function cb, const char* res_name) + : AsyncProgressQueueWorker(cb, res_name) {} +}; + +class TestWorker : public AsyncProgressQueueWorker { + public: + static Napi::Value CreateWork(const CallbackInfo& info) { + int32_t times = info[0].As().Int32Value(); + Function cb = info[1].As(); + Function progress = info[2].As(); + + TestWorker* worker = new TestWorker( + cb, progress, "TestResource", Object::New(info.Env()), times); + + return Napi::External::New(info.Env(), worker); + } + + static void QueueWork(const CallbackInfo& info) { + auto wrap = info[0].As>(); + auto worker = wrap.Data(); + worker->Queue(); + } + + protected: + void Execute(const ExecutionProgress& progress) override { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + + if (_times < 0) { + SetError("test error"); + } else { + progress.Signal(); + } + ProgressData data{0}; + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + } + } + + void OnProgress(const ProgressData* data, size_t count) override { + Napi::Env env = Env(); + _test_case_count++; + if (!_js_progress_cb.IsEmpty()) { + if (_test_case_count == 1) { + if (count != 0) { + SetError("expect 0 count of data on 1st call"); + } + } else { + Number progress = Number::New(env, data->progress); + _js_progress_cb.Call(Receiver().Value(), {progress}); + } + } + } + + private: + TestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource, + int32_t times) + : AsyncProgressQueueWorker(cb, resource_name, resource), _times(times) { + _js_progress_cb.Reset(progress, 1); + } + + int32_t _times; + size_t _test_case_count = 0; + FunctionReference _js_progress_cb; +}; + +} // namespace + +Object InitAsyncProgressQueueWorker(Env env) { + Object exports = Object::New(env); + exports["createWork"] = Function::New(env, TestWorker::CreateWork); + exports["queueWork"] = Function::New(env, TestWorker::QueueWork); + exports["runWorkerNoCb"] = Function::New(env, TestWorkerWithNoCb::DoWork); + exports["runWorkerWithRecv"] = Function::New(env, TestWorkerWithRecv::DoWork); + exports["runWorkerWithCb"] = Function::New(env, TestWorkerWithCb::DoWork); + return exports; +} + +#endif diff --git a/test/async_progress_queue_worker.js b/test/async_progress_queue_worker.js new file mode 100644 index 000000000..b72a55c71 --- /dev/null +++ b/test/async_progress_queue_worker.js @@ -0,0 +1,180 @@ +'use strict'; + +const common = require('./common'); +const assert = require('assert'); + +module.exports = common.runTest(test); +const nodeVersion = process.versions.node.split('.')[0]; + +let asyncHooks; +function checkAsyncHooks () { + if (nodeVersion >= 8) { + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); + } + return true; + } + return false; +} + +async function test ({ asyncprogressqueueworker }) { + await success(asyncprogressqueueworker); + await fail(asyncprogressqueueworker); + + await asyncProgressWorkerCallbackOverloads(asyncprogressqueueworker.runWorkerWithCb); + await asyncProgressWorkerRecvOverloads(asyncprogressqueueworker.runWorkerWithRecv); + await asyncProgressWorkerNoCbOverloads(asyncprogressqueueworker.runWorkerNoCb); +} + +async function asyncProgressWorkerCallbackOverloads (bindingFunction) { + bindingFunction(common.mustCall()); + if (!checkAsyncHooks()) { + return; + } + + const hooks = common.installAysncHooks('cbResources'); + + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + bindingFunction(common.mustCall(), 'cbResources'); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'cbResources', + triggerAsyncId, + resource: {} + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + resolve(); + }).catch((err) => reject(err)); + }); +} + +async function asyncProgressWorkerRecvOverloads (bindingFunction) { + const recvObject = { + a: 4 + }; + + function cb () { + assert.strictEqual(this.a, recvObject.a); + } + + bindingFunction(recvObject, common.mustCall(cb)); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'cbRecvResources', resObject: {} }, + { resName: 'cbRecvResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(recvObject, common.mustCall(cb), asyncResName); + } else { + bindingFunction(recvObject, common.mustCall(cb), asyncResName, asyncResObject); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + resolve(); + }).catch((err) => reject(err)); + }); + } +} + +async function asyncProgressWorkerNoCbOverloads (bindingFunction) { + bindingFunction(common.mustCall()); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'noCbResources', resObject: {} }, + { resName: 'noCbResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(asyncResName, common.mustCall(() => {})); + } else { + bindingFunction(asyncResName, asyncResObject, common.mustCall(() => {})); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + resolve(); + }).catch((err) => reject(err)); + }); + } +} + +function success (binding) { + return new Promise((resolve, reject) => { + const expected = [0, 1, 2, 3]; + const actual = []; + const worker = binding.createWork(expected.length, + common.mustCall((err) => { + if (err) { + reject(err); + } else { + // All queued items shall be invoked before complete callback. + assert.deepEqual(actual, expected); + resolve(); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + }, expected.length) + ); + binding.queueWork(worker); + }); +} + +function fail (binding) { + return new Promise((resolve, reject) => { + const worker = binding.createWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err; }, /test error/); + resolve(); + }), + common.mustNotCall() + ); + binding.queueWork(worker); + }); +} diff --git a/test/async_progress_worker.cc b/test/async_progress_worker.cc new file mode 100644 index 000000000..17aaef3e5 --- /dev/null +++ b/test/async_progress_worker.cc @@ -0,0 +1,357 @@ +#include "napi.h" + +#include +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + size_t progress; +}; + +class TestWorkerWithNoCb : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb); + worker->Queue(); + } break; + + case 2: { + std::string resName = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb); + worker->Queue(); + } break; + + case 3: { + std::string resName = info[0].As(); + Object resObject = info[1].As(); + Function cb = info[2].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), resObject, cb); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress& progress) override { + ProgressData data{1}; + progress.Send(&data, 1); + } + + void OnProgress(const ProgressData*, size_t /* count */) override { + _cb.Call({}); + } + + private: + TestWorkerWithNoCb(Napi::Env env, Function cb) : AsyncProgressWorker(env) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, const char* resourceName, Function cb) + : AsyncProgressWorker(env, resourceName) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, + const char* resourceName, + const Object& resourceObject, + Function cb) + : AsyncProgressWorker(env, resourceName, resourceObject) { + _cb.Reset(cb, 1); + } + FunctionReference _cb; +}; + +class TestWorkerWithRecv : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 2: { + Object recv = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb); + worker->Queue(); + } break; + + case 3: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str()); + worker->Queue(); + } break; + + case 4: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + Object resObject = info[3].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str(), resObject); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithRecv(const Object& recv, const Function& cb) + : AsyncProgressWorker(recv, cb) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName) + : AsyncProgressWorker(recv, cb, resourceName) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName, + const Object& resourceObject) + : AsyncProgressWorker(recv, cb, resourceName, resourceObject) {} +}; + +class TestWorkerWithCb : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithCb* worker = new TestWorkerWithCb(cb); + worker->Queue(); + } break; + + case 2: { + Function cb = info[0].As(); + std::string asyncResName = info[1].As(); + TestWorkerWithCb* worker = + new TestWorkerWithCb(cb, asyncResName.c_str()); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithCb(Function cb) : AsyncProgressWorker(cb) {} + TestWorkerWithCb(Function cb, const char* res_name) + : AsyncProgressWorker(cb, res_name) {} +}; + +class TestWorker : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + int32_t times = info[0].As().Int32Value(); + Function cb = info[1].As(); + Function progress = info[2].As(); + + TestWorker* worker = + new TestWorker(cb, progress, "TestResource", Object::New(info.Env())); + worker->_times = times; + worker->Queue(); + } + + protected: + void Execute(const ExecutionProgress& progress) override { + if (_times < 0) { + SetError("test error"); + } + ProgressData data{0}; + + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + + { + std::unique_lock lk(_cvm); + _cv.wait(lk, [this] { return dataSent; }); + dataSent = false; + } + } + } + + void OnProgress(const ProgressData* data, size_t /* count */) override { + Napi::Env env = Env(); + if (!_progress.IsEmpty()) { + Number progress = Number::New(env, data->progress); + _progress.MakeCallback(Receiver().Value(), {progress}); + } + + { + std::lock_guard lk(_cvm); + dataSent = true; + _cv.notify_one(); + } + } + + private: + TestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker(cb, resource_name, resource) { + _progress.Reset(progress, 1); + } + + bool dataSent = false; + std::condition_variable _cv; + std::mutex _cvm; + int32_t _times; + FunctionReference _progress; +}; + +class MalignWorker : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + Function cb = info[0].As(); + Function progress = info[1].As(); + + MalignWorker* worker = + new MalignWorker(cb, progress, "TestResource", Object::New(info.Env())); + worker->Queue(); + } + + protected: + void Execute(const ExecutionProgress& progress) override { + { + std::unique_lock lock(_cvm); + // Testing a nullptr send is acceptable. + progress.Send(nullptr, 0); + _cv.wait(lock, [this] { return _test_case_count == 1; }); + } + { + std::unique_lock lock(_cvm); + progress.Signal(); + _cv.wait(lock, [this] { return _test_case_count == 2; }); + } + // Testing busy looping on send doesn't trigger unexpected empty data + // OnProgress call. + for (size_t i = 0; i < 1000000; i++) { + ProgressData data{0}; + progress.Send(&data, 1); + } + } + + void OnProgress(const ProgressData* /* data */, size_t count) override { + Napi::Env env = Env(); + { + std::lock_guard lock(_cvm); + _test_case_count++; + } + bool error = false; + Napi::String reason = Napi::String::New(env, "No error"); + if (_test_case_count <= 2 && count != 0) { + error = true; + reason = + Napi::String::New(env, "expect 0 count of data on 1st and 2nd call"); + } + if (_test_case_count > 2 && count != 1) { + error = true; + reason = Napi::String::New( + env, "expect 1 count of data on non-1st and non-2nd call"); + } + _progress.MakeCallback(Receiver().Value(), + {Napi::Boolean::New(env, error), reason}); + _cv.notify_one(); + } + + private: + MalignWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker(cb, resource_name, resource) { + _progress.Reset(progress, 1); + } + + size_t _test_case_count = 0; + std::condition_variable _cv; + std::mutex _cvm; + FunctionReference _progress; +}; + +// Calling a Signal after a SendProgress should not clear progress data +class SignalAfterProgressTestWorker : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + Function cb = info[0].As(); + Function progress = info[1].As(); + + SignalAfterProgressTestWorker* worker = new SignalAfterProgressTestWorker( + cb, progress, "TestResource", Object::New(info.Env())); + worker->Queue(); + } + + protected: + void Execute(const ExecutionProgress& progress) override { + ProgressData data{0}; + progress.Send(&data, 1); + progress.Signal(); + } + + void OnProgress(const ProgressData* /* data */, size_t count) override { + Napi::Env env = Env(); + bool error = false; + Napi::String reason = Napi::String::New(env, "No error"); + if (count != 1) { + error = true; + reason = Napi::String::New(env, "expect 1 count of data"); + } + _progress.MakeCallback(Receiver().Value(), + {Napi::Boolean::New(env, error), reason}); + } + + private: + SignalAfterProgressTestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker(cb, resource_name, resource) { + _progress.Reset(progress, 1); + } + FunctionReference _progress; +}; +} // namespace + +Object InitAsyncProgressWorker(Env env) { + Object exports = Object::New(env); + exports["doWork"] = Function::New(env, TestWorker::DoWork); + exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork); + exports["doSignalAfterProgressTest"] = + Function::New(env, SignalAfterProgressTestWorker::DoWork); + exports["runWorkerNoCb"] = Function::New(env, TestWorkerWithNoCb::DoWork); + exports["runWorkerWithRecv"] = Function::New(env, TestWorkerWithRecv::DoWork); + exports["runWorkerWithCb"] = Function::New(env, TestWorkerWithCb::DoWork); + return exports; +} + +#endif diff --git a/test/async_progress_worker.js b/test/async_progress_worker.js new file mode 100644 index 000000000..d96ab64b8 --- /dev/null +++ b/test/async_progress_worker.js @@ -0,0 +1,200 @@ +'use strict'; + +const common = require('./common'); +const assert = require('assert'); + +module.exports = common.runTest(test); +const nodeVersion = process.versions.node.split('.')[0]; + +let asyncHooks; +function checkAsyncHooks () { + if (nodeVersion >= 8) { + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); + } + return true; + } + return false; +} + +async function test ({ asyncprogressworker }) { + await success(asyncprogressworker); + await fail(asyncprogressworker); + await signalTest(asyncprogressworker.doMalignTest); + await signalTest(asyncprogressworker.doSignalAfterProgressTest); + + await asyncProgressWorkerCallbackOverloads(asyncprogressworker.runWorkerWithCb); + await asyncProgressWorkerRecvOverloads(asyncprogressworker.runWorkerWithRecv); + await asyncProgressWorkerNoCbOverloads(asyncprogressworker.runWorkerNoCb); +} + +async function asyncProgressWorkerCallbackOverloads (bindingFunction) { + bindingFunction(common.mustCall()); + if (!checkAsyncHooks()) { + return; + } + + const hooks = common.installAysncHooks('cbResources'); + + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + bindingFunction(common.mustCall(), 'cbResources'); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'cbResources', + triggerAsyncId, + resource: {} + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); +} + +async function asyncProgressWorkerRecvOverloads (bindingFunction) { + const recvObject = { + a: 4 + }; + + function cb () { + assert.strictEqual(this.a, recvObject.a); + } + + bindingFunction(recvObject, common.mustCall(cb)); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'cbRecvResources', resObject: {} }, + { resName: 'cbRecvResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(recvObject, common.mustCall(cb), asyncResName); + } else { + bindingFunction(recvObject, common.mustCall(cb), asyncResName, asyncResObject); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); + } +} + +async function asyncProgressWorkerNoCbOverloads (bindingFunction) { + bindingFunction(common.mustCall(() => {})); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'noCbResources', resObject: {} }, + { resName: 'noCbResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(asyncResName, common.mustCall(() => {})); + } else { + bindingFunction(asyncResName, asyncResObject, common.mustCall(() => {})); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); + } +} + +function success (binding) { + return new Promise((resolve, reject) => { + const expected = [0, 1, 2, 3]; + const actual = []; + binding.doWork(expected.length, + common.mustCall((err) => { + if (err) { + reject(err); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + if (actual.length === expected.length) { + assert.deepEqual(actual, expected); + resolve(); + } + }, expected.length) + ); + }); +} + +function fail (binding) { + return new Promise((resolve) => { + binding.doWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err; }, /test error/); + resolve(); + }), + common.mustNotCall() + ); + }); +} + +function signalTest (bindingFunction) { + return new Promise((resolve, reject) => { + bindingFunction( + common.mustCall((err) => { + if (err) { + return reject(err); + } + resolve(); + }), + common.mustCallAtLeast((error, reason) => { + try { + assert(!error, reason); + } catch (e) { + reject(e); + } + }, 1) + ); + }); +} diff --git a/test/async_worker.cc b/test/async_worker.cc new file mode 100644 index 000000000..34044e9c8 --- /dev/null +++ b/test/async_worker.cc @@ -0,0 +1,341 @@ +#include +#include +#include +#include +#include "assert.h" +#include "napi.h" + +using namespace Napi; + +class TestWorkerWithUserDefRecv : public AsyncWorker { + public: + static void DoWork(const CallbackInfo& info) { + Object recv = info[0].As(); + Function cb = info[1].As(); + + TestWorkerWithUserDefRecv* worker = new TestWorkerWithUserDefRecv(recv, cb); + worker->Queue(); + } + + static void DoWorkWithAsyncRes(const CallbackInfo& info) { + Object recv = info[0].As(); + Function cb = info[1].As(); + Value resource = info[2]; + + TestWorkerWithUserDefRecv* worker = nullptr; + if (resource == info.Env().Null()) { + worker = new TestWorkerWithUserDefRecv(recv, cb, "TestResource"); + } else { + worker = new TestWorkerWithUserDefRecv( + recv, cb, "TestResource", resource.As()); + } + + worker->Queue(); + } + + protected: + void Execute() override {} + + private: + TestWorkerWithUserDefRecv(const Object& recv, const Function& cb) + : AsyncWorker(recv, cb) {} + TestWorkerWithUserDefRecv(const Object& recv, + const Function& cb, + const char* resource_name) + : AsyncWorker(recv, cb, resource_name) {} + TestWorkerWithUserDefRecv(const Object& recv, + const Function& cb, + const char* resource_name, + const Object& resource) + : AsyncWorker(recv, cb, resource_name, resource) {} +}; + +// Using default std::allocator impl, but assuming user can define their own +// allocate/deallocate methods +class CustomAllocWorker : public AsyncWorker { + using Allocator = std::allocator; + + public: + CustomAllocWorker(Function& cb) : AsyncWorker(cb){}; + static void DoWork(const CallbackInfo& info) { + Function cb = info[0].As(); + Allocator allocator; + CustomAllocWorker* newWorker = allocator.allocate(1); + std::allocator_traits::construct(allocator, newWorker, cb); + newWorker->Queue(); + } + + protected: + void Execute() override {} + void Destroy() override { + assert(this->_secretVal == 24); + Allocator allocator; + std::allocator_traits::destroy(allocator, this); + allocator.deallocate(this, 1); + } + + private: + int _secretVal = 24; +}; + +class TestWorker : public AsyncWorker { + public: + static void DoWork(const CallbackInfo& info) { + bool succeed = info[0].As(); + Object resource = info[1].As(); + Function cb = info[2].As(); + Value data = info[3]; + + TestWorker* worker = nullptr; + if (resource == info.Env().Null()) { + worker = new TestWorker(cb, "TestResource"); + } else { + worker = new TestWorker(cb, "TestResource", resource); + } + + worker->Receiver().Set("data", data); + worker->_succeed = succeed; + worker->Queue(); + } + + protected: + void Execute() override { + if (!_succeed) { + SetError("test error"); + } + } + + private: + TestWorker(Function cb, const char* resource_name, const Object& resource) + : AsyncWorker(cb, resource_name, resource) {} + TestWorker(Function cb, const char* resource_name) + : AsyncWorker(cb, resource_name) {} + bool _succeed{}; +}; + +class TestWorkerWithResult : public AsyncWorker { + public: + static void DoWork(const CallbackInfo& info) { + bool succeed = info[0].As(); + Object resource = info[1].As(); + Function cb = info[2].As(); + Value data = info[3]; + + TestWorkerWithResult* worker = + new TestWorkerWithResult(cb, "TestResource", resource); + worker->Receiver().Set("data", data); + worker->_succeed = succeed; + worker->Queue(); + } + + protected: + void Execute() override { + if (!_succeed) { + SetError("test error"); + } + } + + std::vector GetResult(Napi::Env env) override { + return {Boolean::New(env, _succeed), + String::New(env, _succeed ? "ok" : "error")}; + } + + private: + TestWorkerWithResult(Function cb, + const char* resource_name, + const Object& resource) + : AsyncWorker(cb, resource_name, resource) {} + bool _succeed{}; +}; + +class TestWorkerNoCallback : public AsyncWorker { + public: + static Value DoWork(const CallbackInfo& info) { + bool succeed = info[0].As(); + + TestWorkerNoCallback* worker = new TestWorkerNoCallback(info.Env()); + worker->_succeed = succeed; + worker->Queue(); + return worker->_deferred.Promise(); + } + + static Value DoWorkWithAsyncRes(const CallbackInfo& info) { + napi_env env = info.Env(); + bool succeed = info[0].As(); + Object resource = info[1].As(); + + TestWorkerNoCallback* worker = nullptr; + if (resource == info.Env().Null()) { + worker = new TestWorkerNoCallback(env, "TestResource"); + } else { + worker = new TestWorkerNoCallback(env, "TestResource", resource); + } + worker->_succeed = succeed; + worker->Queue(); + return worker->_deferred.Promise(); + } + + protected: + void Execute() override {} + virtual void OnOK() override { _deferred.Resolve(Env().Undefined()); } + virtual void OnError(const Napi::Error& /* e */) override { + _deferred.Reject(Env().Undefined()); + } + + private: + TestWorkerNoCallback(Napi::Env env) + : AsyncWorker(env), _deferred(Napi::Promise::Deferred::New(env)) {} + + TestWorkerNoCallback(napi_env env, const char* resource_name) + : AsyncWorker(env, resource_name), + _deferred(Napi::Promise::Deferred::New(env)) {} + + TestWorkerNoCallback(napi_env env, + const char* resource_name, + const Object& resource) + : AsyncWorker(env, resource_name, resource), + _deferred(Napi::Promise::Deferred::New(env)) {} + Promise::Deferred _deferred; + bool _succeed{}; +}; + +class EchoWorker : public AsyncWorker { + public: + EchoWorker(Function& cb, std::string& echo) : AsyncWorker(cb), echo(echo) {} + ~EchoWorker() {} + + void Execute() override { + // Simulate cpu heavy task + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } + + void OnOK() override { + HandleScope scope(Env()); + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } + + private: + std::string echo; +}; + +class FailCancelWorker : public AsyncWorker { + private: + bool taskIsRunning = false; + std::mutex mu; + std::condition_variable taskStartingCv; + void NotifyJSThreadTaskHasStarted() { + { + std::lock_guard lk(mu); + taskIsRunning = true; + taskStartingCv.notify_one(); + } + } + + public: + FailCancelWorker(Function& cb) : AsyncWorker(cb) {} + ~FailCancelWorker() {} + + void WaitForWorkerTaskToStart() { + std::unique_lock lk(mu); + taskStartingCv.wait(lk, [this] { return taskIsRunning; }); + taskIsRunning = false; + } + + static void DoCancel(const CallbackInfo& info) { + Function cb = info[0].As(); + + FailCancelWorker* cancelWorker = new FailCancelWorker(cb); + cancelWorker->Queue(); + cancelWorker->WaitForWorkerTaskToStart(); + +#ifdef NAPI_CPP_EXCEPTIONS + try { + cancelWorker->Cancel(); + } catch (Napi::Error&) { + Napi::Error::New(info.Env(), "Unable to cancel async worker tasks") + .ThrowAsJavaScriptException(); + } +#else + cancelWorker->Cancel(); +#endif + } + + void Execute() override { + NotifyJSThreadTaskHasStarted(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + void OnOK() override {} + + void OnError(const Error&) override {} +}; + +class CancelWorker : public AsyncWorker { + public: + CancelWorker(Function& cb) : AsyncWorker(cb) {} + ~CancelWorker() {} + + static void DoWork(const CallbackInfo& info) { + Function cb = info[0].As(); + std::string echo = info[1].As(); + int threadNum = info[2].As().Uint32Value(); + + for (int i = 0; i < threadNum; i++) { + AsyncWorker* worker = new EchoWorker(cb, echo); + worker->Queue(); + assert(worker->Env() == info.Env()); + } + + AsyncWorker* cancelWorker = new CancelWorker(cb); + cancelWorker->Queue(); + +#ifdef NAPI_CPP_EXCEPTIONS + try { + cancelWorker->Cancel(); + } catch (Napi::Error&) { + Napi::Error::New(info.Env(), "Unable to cancel async worker tasks") + .ThrowAsJavaScriptException(); + } +#else + cancelWorker->Cancel(); +#endif + } + + void Execute() override { + // Simulate cpu heavy task + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + void OnOK() override { + Napi::Error::New(this->Env(), + "OnOk should not be invoked on successful cancellation") + .ThrowAsJavaScriptException(); + } + + void OnError(const Error&) override { + Napi::Error::New(this->Env(), + "OnError should not be invoked on successful cancellation") + .ThrowAsJavaScriptException(); + } +}; + +Object InitAsyncWorker(Env env) { + Object exports = Object::New(env); + exports["doWorkRecv"] = Function::New(env, TestWorkerWithUserDefRecv::DoWork); + exports["doWithRecvAsyncRes"] = + Function::New(env, TestWorkerWithUserDefRecv::DoWorkWithAsyncRes); + exports["doWork"] = Function::New(env, TestWorker::DoWork); + exports["doWorkAsyncResNoCallback"] = + Function::New(env, TestWorkerNoCallback::DoWorkWithAsyncRes); + exports["doWorkNoCallback"] = + Function::New(env, TestWorkerNoCallback::DoWork); + exports["doWorkWithResult"] = + Function::New(env, TestWorkerWithResult::DoWork); + exports["tryCancelQueuedWork"] = Function::New(env, CancelWorker::DoWork); + + exports["expectCancelToFail"] = + Function::New(env, FailCancelWorker::DoCancel); + exports["expectCustomAllocWorkerToDealloc"] = + Function::New(env, CustomAllocWorker::DoWork); + return exports; +} diff --git a/test/async_worker.js b/test/async_worker.js new file mode 100644 index 000000000..16e2a8e96 --- /dev/null +++ b/test/async_worker.js @@ -0,0 +1,264 @@ +'use strict'; +const assert = require('assert'); +const common = require('./common'); + +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0]; +let asyncHooks; +function checkAsyncHooks () { + if (nodeVersion >= 8) { + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); + } + return true; + } + return false; +} + +module.exports = common.runTest(test); + +function installAsyncHooksForTest () { + return new Promise((resolve, reject) => { + let id; + const events = []; + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let destroyed = false; + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + + const hook = asyncHooks.createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === 'TestResource') { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + destroyed = true; + } + } + }).enable(); + }); +} + +async function test (binding) { + const libUvThreadCount = Number(process.env.UV_THREADPOOL_SIZE || 4); + binding.asyncworker.tryCancelQueuedWork(() => {}, 'echoString', libUvThreadCount); + + let taskFailed = false; + try { + binding.asyncworker.expectCancelToFail(() => {}); + } catch (e) { + taskFailed = true; + } + + assert.equal(taskFailed, true, 'We expect task cancellation to fail'); + + if (!checkAsyncHooks()) { + binding.asyncworker.expectCustomAllocWorkerToDealloc(() => {}); + + await new Promise((resolve) => { + const obj = { data: 'test data' }; + binding.asyncworker.doWorkRecv(obj, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }); + }); + + await new Promise((resolve) => { + binding.asyncworker.doWork(true, null, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await new Promise((resolve) => { + binding.asyncworker.doWork(false, {}, function (e) { + assert.ok(e instanceof Error); + assert.strictEqual(e.message, 'test error'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await new Promise((resolve) => { + binding.asyncworker.doWorkWithResult(true, {}, function (succeed, succeedString) { + assert(arguments.length === 2); + assert(succeed); + assert(succeedString === 'ok'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + return; + } + + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve) => { + const recvObj = { data: 'test data' }; + binding.asyncworker.doWithRecvAsyncRes(recvObj, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, { foo: 'fooBar' }); + }); + + await hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'TestResource', + triggerAsyncId, + resource: { foo: 'fooBar' } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } + + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve) => { + const recvObj = { data: 'test data' }; + binding.asyncworker.doWithRecvAsyncRes(recvObj, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, null); + }); + + await hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'TestResource', + triggerAsyncId, + resource: { } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } + + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve) => { + binding.asyncworker.doWork(true, { foo: 'foo' }, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'TestResource', + triggerAsyncId, + resource: { foo: 'foo' } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } + + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve) => { + binding.asyncworker.doWorkWithResult(true, { foo: 'foo' }, + function (succeed, succeedString) { + assert(arguments.length === 2); + assert(succeed); + assert(succeedString === 'ok'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'TestResource', + triggerAsyncId, + resource: { foo: 'foo' } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } + + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve) => { + binding.asyncworker.doWork(false, { foo: 'foo' }, function (e) { + assert.ok(e instanceof Error); + assert.strictEqual(e.message, 'test error'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'TestResource', + triggerAsyncId, + resource: { foo: 'foo' } + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } +} diff --git a/test/async_worker_nocallback.js b/test/async_worker_nocallback.js new file mode 100644 index 000000000..2f848c4e9 --- /dev/null +++ b/test/async_worker_nocallback.js @@ -0,0 +1,19 @@ +'use strict'; + +const common = require('./common'); + +module.exports = common.runTest(test); + +async function test (binding) { + await binding.asyncworker.doWorkAsyncResNoCallback(true, {}) + .then(common.mustCall()).catch(common.mustNotCall()); + + await binding.asyncworker.doWorkAsyncResNoCallback(false, {}) + .then(common.mustNotCall()).catch(common.mustCall()); + + await binding.asyncworker.doWorkNoCallback(false) + .then(common.mustNotCall()).catch(common.mustCall()); + + await binding.asyncworker.doWorkNoCallback(true) + .then(common.mustNotCall()).catch(common.mustCall()); +} diff --git a/test/asyncworker-persistent.cc b/test/async_worker_persistent.cc similarity index 80% rename from test/asyncworker-persistent.cc rename to test/async_worker_persistent.cc index 97aa0cab8..90349ae34 100644 --- a/test/asyncworker-persistent.cc +++ b/test/async_worker_persistent.cc @@ -9,7 +9,7 @@ using namespace Napi; namespace { class PersistentTestWorker : public AsyncWorker { -public: + public: static PersistentTestWorker* current_worker; static void DoWork(const CallbackInfo& info) { bool succeed = info[0].As(); @@ -28,24 +28,21 @@ class PersistentTestWorker : public AsyncWorker { } static void DeleteWorker(const CallbackInfo& info) { - (void) info; + (void)info; delete current_worker; } - ~PersistentTestWorker() { - current_worker = nullptr; - } + ~PersistentTestWorker() { current_worker = nullptr; } -protected: + protected: void Execute() override { if (!_succeed) { SetError("test error"); } } -private: - PersistentTestWorker(Function cb, - const char* resource_name) + private: + PersistentTestWorker(Function cb, const char* resource_name) : AsyncWorker(cb, resource_name) {} bool _succeed; @@ -58,9 +55,8 @@ PersistentTestWorker* PersistentTestWorker::current_worker = nullptr; Object InitPersistentAsyncWorker(Env env) { Object exports = Object::New(env); exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork); - exports.DefineProperty( - PropertyDescriptor::Accessor(env, exports, "workerGone", - PersistentTestWorker::GetWorkerGone)); + exports.DefineProperty(PropertyDescriptor::Accessor( + env, exports, "workerGone", PersistentTestWorker::GetWorkerGone)); exports["deleteWorker"] = Function::New(env, PersistentTestWorker::DeleteWorker); return exports; diff --git a/test/asyncworker-persistent.js b/test/async_worker_persistent.js similarity index 50% rename from test/asyncworker-persistent.js rename to test/async_worker_persistent.js index d584086e7..d3ce11e23 100644 --- a/test/asyncworker-persistent.js +++ b/test/async_worker_persistent.js @@ -1,15 +1,12 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -const common = require('./common'); -const binding = require(`./build/${buildType}/binding.node`); -const noexceptBinding = require(`./build/${buildType}/binding_noexcept.node`); -function test(binding, succeed) { +function test (binding, succeed) { return new Promise((resolve) => // Can't pass an arrow function to doWork because that results in an // undefined context inside its body when the function gets called. - binding.doWork(succeed, function(e) { + binding.doWork(succeed, function (e) { setImmediate(() => { // If the work is supposed to fail, make sure there's an error. assert.strictEqual(succeed || e.message === 'test error', true); @@ -21,7 +18,7 @@ function test(binding, succeed) { })); } -module.exports = test(binding.persistentasyncworker, false) - .then(() => test(binding.persistentasyncworker, true)) - .then(() => test(noexceptBinding.persistentasyncworker, false)) - .then(() => test(noexceptBinding.persistentasyncworker, true)); +module.exports = require('./common').runTest(async binding => { + await test(binding.persistentasyncworker, false); + await test(binding.persistentasyncworker, true); +}); diff --git a/test/asynccontext.cc b/test/asynccontext.cc deleted file mode 100644 index bb1acbb89..000000000 --- a/test/asynccontext.cc +++ /dev/null @@ -1,21 +0,0 @@ -#include "napi.h" - -using namespace Napi; - -namespace { - -static void MakeCallback(const CallbackInfo& info) { - Function callback = info[0].As(); - Object resource = info[1].As(); - AsyncContext context(info.Env(), "async_context_test", resource); - callback.MakeCallback(Object::New(info.Env()), - std::initializer_list{}, context); -} - -} // end anonymous namespace - -Object InitAsyncContext(Env env) { - Object exports = Object::New(env); - exports["makeCallback"] = Function::New(env, MakeCallback); - return exports; -} diff --git a/test/asynccontext.js b/test/asynccontext.js deleted file mode 100644 index e9b4aabc3..000000000 --- a/test/asynccontext.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const common = require('./common'); - -// we only check async hooks on 8.x an higher were -// they are closer to working properly -const nodeVersion = process.versions.node.split('.')[0] -let async_hooks = undefined; -function checkAsyncHooks() { - if (nodeVersion >= 8) { - if (async_hooks == undefined) { - async_hooks = require('async_hooks'); - } - return true; - } - return false; -} - -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); - -function installAsyncHooksForTest() { - return new Promise((resolve, reject) => { - let id; - const events = []; - const hook = async_hooks.createHook({ - init(asyncId, type, triggerAsyncId, resource) { - if (id === undefined && type === 'async_context_test') { - id = asyncId; - events.push({ eventName: 'init', type, triggerAsyncId, resource }); - } - }, - before(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'before' }); - } - }, - after(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'after' }); - } - }, - destroy(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'destroy' }); - hook.disable(); - resolve(events); - } - } - }).enable(); - }); -} - -function test(binding) { - binding.asynccontext.makeCallback(common.mustCall(), { foo: 'foo' }); - if (!checkAsyncHooks()) - return; - - const hooks = installAsyncHooksForTest(); - const triggerAsyncId = async_hooks.executionAsyncId(); - hooks.then(actual => { - assert.deepStrictEqual(actual, [ - { eventName: 'init', - type: 'async_context_test', - triggerAsyncId: triggerAsyncId, - resource: { foo: 'foo' } }, - { eventName: 'before' }, - { eventName: 'after' }, - { eventName: 'destroy' } - ]); - }).catch(common.mustNotCall()); -} diff --git a/test/asyncprogressqueueworker.cc b/test/asyncprogressqueueworker.cc deleted file mode 100644 index 23c6707ff..000000000 --- a/test/asyncprogressqueueworker.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "napi.h" - -#include -#include -#include -#include - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace { - -struct ProgressData { - int32_t progress; -}; - -class TestWorker : public AsyncProgressQueueWorker { -public: - static Napi::Value CreateWork(const CallbackInfo& info) { - int32_t times = info[0].As().Int32Value(); - Function cb = info[1].As(); - Function progress = info[2].As(); - - TestWorker* worker = new TestWorker(cb, - progress, - "TestResource", - Object::New(info.Env()), - times); - - return Napi::External::New(info.Env(), worker); - } - - static void QueueWork(const CallbackInfo& info) { - auto wrap = info[0].As>(); - auto worker = wrap.Data(); - worker->Queue(); - } - -protected: - void Execute(const ExecutionProgress& progress) override { - using namespace std::chrono_literals; - std::this_thread::sleep_for(1s); - - if (_times < 0) { - SetError("test error"); - } - ProgressData data{0}; - for (int32_t idx = 0; idx < _times; idx++) { - data.progress = idx; - progress.Send(&data, 1); - } - } - - void OnProgress(const ProgressData* data, size_t /* count */) override { - Napi::Env env = Env(); - if (!_js_progress_cb.IsEmpty()) { - Number progress = Number::New(env, data->progress); - _js_progress_cb.Call(Receiver().Value(), { progress }); - } - } - -private: - TestWorker(Function cb, - Function progress, - const char* resource_name, - const Object& resource, - int32_t times) - : AsyncProgressQueueWorker(cb, resource_name, resource), - _times(times) { - _js_progress_cb.Reset(progress, 1); - } - - int32_t _times; - FunctionReference _js_progress_cb; -}; - -} // namespace - -Object InitAsyncProgressQueueWorker(Env env) { - Object exports = Object::New(env); - exports["createWork"] = Function::New(env, TestWorker::CreateWork); - exports["queueWork"] = Function::New(env, TestWorker::QueueWork); - return exports; -} - -#endif diff --git a/test/asyncprogressqueueworker.js b/test/asyncprogressqueueworker.js deleted file mode 100644 index 4bb525e9a..000000000 --- a/test/asyncprogressqueueworker.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const common = require('./common') -const assert = require('assert'); -const os = require('os'); - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); - -async function test({ asyncprogressqueueworker }) { - await success(asyncprogressqueueworker); - await fail(asyncprogressqueueworker); -} - -function success(binding) { - return new Promise((resolve, reject) => { - const expected = [0, 1, 2, 3]; - const actual = []; - const worker = binding.createWork(expected.length, - common.mustCall((err) => { - if (err) { - reject(err); - } else { - // All queued items shall be invoked before complete callback. - assert.deepEqual(actual, expected); - resolve(); - } - }), - common.mustCall((_progress) => { - actual.push(_progress); - }, expected.length) - ); - binding.queueWork(worker); - }); -} - -function fail(binding) { - return new Promise((resolve, reject) => { - const worker = binding.createWork(-1, - common.mustCall((err) => { - assert.throws(() => { throw err }, /test error/); - resolve(); - }), - common.mustNotCall() - ); - binding.queueWork(worker); - }); -} diff --git a/test/asyncprogressworker.cc b/test/asyncprogressworker.cc deleted file mode 100644 index 1705124ad..000000000 --- a/test/asyncprogressworker.cc +++ /dev/null @@ -1,130 +0,0 @@ -#include "napi.h" - -#include -#include -#include -#include - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace { - -struct ProgressData { - size_t progress; -}; - -class TestWorker : public AsyncProgressWorker { -public: - static void DoWork(const CallbackInfo& info) { - int32_t times = info[0].As().Int32Value(); - Function cb = info[1].As(); - Function progress = info[2].As(); - - TestWorker* worker = new TestWorker(cb, progress, "TestResource", Object::New(info.Env())); - worker->_times = times; - worker->Queue(); - } - -protected: - void Execute(const ExecutionProgress& progress) override { - if (_times < 0) { - SetError("test error"); - } - ProgressData data{0}; - std::unique_lock lock(_cvm); - for (int32_t idx = 0; idx < _times; idx++) { - data.progress = idx; - progress.Send(&data, 1); - _cv.wait(lock); - } - } - - void OnProgress(const ProgressData* data, size_t /* count */) override { - Napi::Env env = Env(); - if (!_progress.IsEmpty()) { - Number progress = Number::New(env, data->progress); - _progress.MakeCallback(Receiver().Value(), { progress }); - } - _cv.notify_one(); - } - -private: - TestWorker(Function cb, Function progress, const char* resource_name, const Object& resource) - : AsyncProgressWorker(cb, resource_name, resource) { - _progress.Reset(progress, 1); - } - std::condition_variable _cv; - std::mutex _cvm; - int32_t _times; - FunctionReference _progress; -}; - -class MalignWorker : public AsyncProgressWorker { - public: - static void DoWork(const CallbackInfo& info) { - Function cb = info[0].As(); - Function progress = info[1].As(); - - MalignWorker* worker = - new MalignWorker(cb, progress, "TestResource", Object::New(info.Env())); - worker->Queue(); - } - - protected: - void Execute(const ExecutionProgress& progress) override { - std::unique_lock lock(_cvm); - // Testing a nullptr send is acceptable. - progress.Send(nullptr, 0); - _cv.wait(lock); - // Testing busy looping on send doesn't trigger unexpected empty data - // OnProgress call. - for (size_t i = 0; i < 1000000; i++) { - ProgressData data{0}; - progress.Send(&data, 1); - } - } - - void OnProgress(const ProgressData* /* data */, size_t count) override { - Napi::Env env = Env(); - _test_case_count++; - bool error = false; - Napi::String reason = Napi::String::New(env, "No error"); - if (_test_case_count == 1 && count != 0) { - error = true; - reason = Napi::String::New(env, "expect 0 count of data on 1st call"); - } - if (_test_case_count > 1 && count != 1) { - error = true; - reason = Napi::String::New(env, "expect 1 count of data on non-1st call"); - } - _progress.MakeCallback(Receiver().Value(), - {Napi::Boolean::New(env, error), reason}); - _cv.notify_one(); - } - - private: - MalignWorker(Function cb, - Function progress, - const char* resource_name, - const Object& resource) - : AsyncProgressWorker(cb, resource_name, resource) { - _progress.Reset(progress, 1); - } - - size_t _test_case_count = 0; - std::condition_variable _cv; - std::mutex _cvm; - FunctionReference _progress; -}; -} - -Object InitAsyncProgressWorker(Env env) { - Object exports = Object::New(env); - exports["doWork"] = Function::New(env, TestWorker::DoWork); - exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork); - return exports; -} - -#endif diff --git a/test/asyncprogressworker.js b/test/asyncprogressworker.js deleted file mode 100644 index 285fd2a91..000000000 --- a/test/asyncprogressworker.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const common = require('./common') -const assert = require('assert'); - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); - -async function test({ asyncprogressworker }) { - await success(asyncprogressworker); - await fail(asyncprogressworker); - await malignTest(asyncprogressworker); -} - -function success(binding) { - return new Promise((resolve, reject) => { - const expected = [0, 1, 2, 3]; - const actual = []; - binding.doWork(expected.length, - common.mustCall((err) => { - if (err) { - reject(err); - } - }), - common.mustCall((_progress) => { - actual.push(_progress); - if (actual.length === expected.length) { - assert.deepEqual(actual, expected); - resolve(); - } - }, expected.length) - ); - }); -} - -function fail(binding) { - return new Promise((resolve) => { - binding.doWork(-1, - common.mustCall((err) => { - assert.throws(() => { throw err }, /test error/) - resolve(); - }), - common.mustNotCall() - ); - }); -} - -function malignTest(binding) { - return new Promise((resolve, reject) => { - binding.doMalignTest( - common.mustCall((err) => { - if (err) { - return reject(err); - } - resolve(); - }), - common.mustCallAtLeast((error, reason) => { - assert(!error, reason); - }, 1) - ); - }); -} diff --git a/test/asyncworker-nocallback.js b/test/asyncworker-nocallback.js deleted file mode 100644 index fa9c172c0..000000000 --- a/test/asyncworker-nocallback.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const common = require('./common'); - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); - -async function test(binding) { - await binding.asyncworker.doWorkNoCallback(true, {}) - .then(common.mustCall()).catch(common.mustNotCall()); - - await binding.asyncworker.doWorkNoCallback(false, {}) - .then(common.mustNotCall()).catch(common.mustCall()); -} diff --git a/test/asyncworker.cc b/test/asyncworker.cc deleted file mode 100644 index 324146533..000000000 --- a/test/asyncworker.cc +++ /dev/null @@ -1,102 +0,0 @@ -#include "napi.h" - -using namespace Napi; - -class TestWorker : public AsyncWorker { -public: - static void DoWork(const CallbackInfo& info) { - bool succeed = info[0].As(); - Object resource = info[1].As(); - Function cb = info[2].As(); - Value data = info[3]; - - TestWorker* worker = new TestWorker(cb, "TestResource", resource); - worker->Receiver().Set("data", data); - worker->_succeed = succeed; - worker->Queue(); - } - -protected: - void Execute() override { - if (!_succeed) { - SetError("test error"); - } - } - -private: - TestWorker(Function cb, const char* resource_name, const Object& resource) - : AsyncWorker(cb, resource_name, resource) {} - bool _succeed; -}; - -class TestWorkerWithResult : public AsyncWorker { -public: - static void DoWork(const CallbackInfo& info) { - bool succeed = info[0].As(); - Object resource = info[1].As(); - Function cb = info[2].As(); - Value data = info[3]; - - TestWorkerWithResult* worker = new TestWorkerWithResult(cb, "TestResource", resource); - worker->Receiver().Set("data", data); - worker->_succeed = succeed; - worker->Queue(); - } - -protected: - void Execute() override { - if (!_succeed) { - SetError("test error"); - } - } - - std::vector GetResult(Napi::Env env) override { - return {Boolean::New(env, _succeed), - String::New(env, _succeed ? "ok" : "error")}; - } - -private: - TestWorkerWithResult(Function cb, const char* resource_name, const Object& resource) - : AsyncWorker(cb, resource_name, resource) {} - bool _succeed; -}; - -class TestWorkerNoCallback : public AsyncWorker { -public: - static Value DoWork(const CallbackInfo& info) { - napi_env env = info.Env(); - bool succeed = info[0].As(); - Object resource = info[1].As(); - - TestWorkerNoCallback* worker = new TestWorkerNoCallback(env, "TestResource", resource); - worker->_succeed = succeed; - worker->Queue(); - return worker->_deferred.Promise(); - } - -protected: - void Execute() override { - } - virtual void OnOK() override { - _deferred.Resolve(Env().Undefined()); - - } - virtual void OnError(const Napi::Error& /* e */) override { - _deferred.Reject(Env().Undefined()); - } - -private: - TestWorkerNoCallback(napi_env env, const char* resource_name, const Object& resource) - : AsyncWorker(env, resource_name, resource), _deferred(Napi::Promise::Deferred::New(env)) { - } - Promise::Deferred _deferred; - bool _succeed; -}; - -Object InitAsyncWorker(Env env) { - Object exports = Object::New(env); - exports["doWork"] = Function::New(env, TestWorker::DoWork); - exports["doWorkNoCallback"] = Function::New(env, TestWorkerNoCallback::DoWork); - exports["doWorkWithResult"] = Function::New(env, TestWorkerWithResult::DoWork); - return exports; -} diff --git a/test/asyncworker.js b/test/asyncworker.js deleted file mode 100644 index 0f008bb33..000000000 --- a/test/asyncworker.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const common = require('./common'); - -// we only check async hooks on 8.x an higher were -// they are closer to working properly -const nodeVersion = process.versions.node.split('.')[0] -let async_hooks = undefined; -function checkAsyncHooks() { - if (nodeVersion >=8) { - if (async_hooks == undefined) { - async_hooks = require('async_hooks'); - } - return true; - } - return false; -} - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); - -function installAsyncHooksForTest() { - return new Promise((resolve, reject) => { - let id; - const events = []; - const hook = async_hooks.createHook({ - init(asyncId, type, triggerAsyncId, resource) { - if (id === undefined && type === 'TestResource') { - id = asyncId; - events.push({ eventName: 'init', type, triggerAsyncId, resource }); - } - }, - before(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'before' }); - } - }, - after(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'after' }); - } - }, - destroy(asyncId) { - if (asyncId === id) { - events.push({ eventName: 'destroy' }); - hook.disable(); - resolve(events); - } - } - }).enable(); - }); -} - -async function test(binding) { - if (!checkAsyncHooks()) { - await new Promise((resolve) => { - binding.asyncworker.doWork(true, {}, function (e) { - assert.strictEqual(typeof e, 'undefined'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - await new Promise((resolve) => { - binding.asyncworker.doWork(false, {}, function (e) { - assert.ok(e instanceof Error); - assert.strictEqual(e.message, 'test error'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - await new Promise((resolve) => { - binding.asyncworker.doWorkWithResult(true, {}, function (succeed, succeedString) { - assert(arguments.length == 2); - assert(succeed); - assert(succeedString == "ok"); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - return; - } - - { - const hooks = installAsyncHooksForTest(); - const triggerAsyncId = async_hooks.executionAsyncId(); - await new Promise((resolve) => { - binding.asyncworker.doWork(true, { foo: 'foo' }, function (e) { - assert.strictEqual(typeof e, 'undefined'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - await hooks.then(actual => { - assert.deepStrictEqual(actual, [ - { eventName: 'init', - type: 'TestResource', - triggerAsyncId: triggerAsyncId, - resource: { foo: 'foo' } }, - { eventName: 'before' }, - { eventName: 'after' }, - { eventName: 'destroy' } - ]); - }).catch(common.mustNotCall()); - } - - { - const hooks = installAsyncHooksForTest(); - const triggerAsyncId = async_hooks.executionAsyncId(); - await new Promise((resolve) => { - binding.asyncworker.doWorkWithResult(true, { foo: 'foo' }, - function (succeed, succeedString) { - assert(arguments.length == 2); - assert(succeed); - assert(succeedString == "ok"); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - await hooks.then(actual => { - assert.deepStrictEqual(actual, [ - { eventName: 'init', - type: 'TestResource', - triggerAsyncId: triggerAsyncId, - resource: { foo: 'foo' } }, - { eventName: 'before' }, - { eventName: 'after' }, - { eventName: 'destroy' } - ]); - }).catch(common.mustNotCall()); - } - - { - const hooks = installAsyncHooksForTest(); - const triggerAsyncId = async_hooks.executionAsyncId(); - await new Promise((resolve) => { - binding.asyncworker.doWork(false, { foo: 'foo' }, function (e) { - assert.ok(e instanceof Error); - assert.strictEqual(e.message, 'test error'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - resolve(); - }, 'test data'); - }); - - await hooks.then(actual => { - assert.deepStrictEqual(actual, [ - { eventName: 'init', - type: 'TestResource', - triggerAsyncId: triggerAsyncId, - resource: { foo: 'foo' } }, - { eventName: 'before' }, - { eventName: 'after' }, - { eventName: 'destroy' } - ]); - }).catch(common.mustNotCall()); - } -} diff --git a/test/basic_types/array.js b/test/basic_types/array.js index 38ccba448..a4bb69a66 100644 --- a/test/basic_types/array.js +++ b/test/basic_types/array.js @@ -1,12 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - -function test(binding) { +module.exports = require('../common').runTest(test); +function test (binding) { // create empty array const array = binding.basic_types_array.createArray(); assert.strictEqual(binding.basic_types_array.getLength(array), 0); @@ -17,7 +14,7 @@ function test(binding) { // set function test binding.basic_types_array.set(array, 0, 10); - binding.basic_types_array.set(array, 1, "test"); + binding.basic_types_array.set(array, 1, 'test'); binding.basic_types_array.set(array, 2, 3.0); // check length after set data @@ -25,7 +22,7 @@ function test(binding) { // get function test assert.strictEqual(binding.basic_types_array.get(array, 0), 10); - assert.strictEqual(binding.basic_types_array.get(array, 1), "test"); + assert.strictEqual(binding.basic_types_array.get(array, 1), 'test'); assert.strictEqual(binding.basic_types_array.get(array, 2), 3.0); // overwrite test diff --git a/test/basic_types/boolean.cc b/test/basic_types/boolean.cc index 4abacece4..9e67eed25 100644 --- a/test/basic_types/boolean.cc +++ b/test/basic_types/boolean.cc @@ -31,8 +31,10 @@ Object InitBasicTypesBoolean(Env env) { exports["createBoolean"] = Function::New(env, CreateBoolean); exports["createEmptyBoolean"] = Function::New(env, CreateEmptyBoolean); - exports["createBooleanFromExistingValue"] = Function::New(env, CreateBooleanFromExistingValue); - exports["createBooleanFromPrimitive"] = Function::New(env, CreateBooleanFromPrimitive); + exports["createBooleanFromExistingValue"] = + Function::New(env, CreateBooleanFromExistingValue); + exports["createBooleanFromPrimitive"] = + Function::New(env, CreateBooleanFromPrimitive); exports["operatorBool"] = Function::New(env, OperatorBool); return exports; } diff --git a/test/basic_types/boolean.js b/test/basic_types/boolean.js index 13817ee52..61f005ef9 100644 --- a/test/basic_types/boolean.js +++ b/test/basic_types/boolean.js @@ -1,11 +1,10 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { +function test (binding) { const bool1 = binding.basic_types_boolean.createBoolean(true); assert.strictEqual(bool1, true); @@ -32,5 +31,4 @@ function test(binding) { const bool8 = binding.basic_types_boolean.operatorBool(false); assert.strictEqual(bool8, false); - } diff --git a/test/basic_types/number.cc b/test/basic_types/number.cc index 4ccb844b5..ee5c815ef 100644 --- a/test/basic_types/number.cc +++ b/test/basic_types/number.cc @@ -43,27 +43,32 @@ Value MaxDouble(const CallbackInfo& info) { Value OperatorInt32(const CallbackInfo& info) { Number number = info[0].As(); - return Boolean::New(info.Env(), number.Int32Value() == static_cast(number)); + return Boolean::New(info.Env(), + number.Int32Value() == static_cast(number)); } Value OperatorUint32(const CallbackInfo& info) { Number number = info[0].As(); - return Boolean::New(info.Env(), number.Uint32Value() == static_cast(number)); + return Boolean::New(info.Env(), + number.Uint32Value() == static_cast(number)); } Value OperatorInt64(const CallbackInfo& info) { Number number = info[0].As(); - return Boolean::New(info.Env(), number.Int64Value() == static_cast(number)); + return Boolean::New(info.Env(), + number.Int64Value() == static_cast(number)); } Value OperatorFloat(const CallbackInfo& info) { Number number = info[0].As(); - return Boolean::New(info.Env(), number.FloatValue() == static_cast(number)); + return Boolean::New(info.Env(), + number.FloatValue() == static_cast(number)); } Value OperatorDouble(const CallbackInfo& info) { Number number = info[0].As(); - return Boolean::New(info.Env(), number.DoubleValue() == static_cast(number)); + return Boolean::New(info.Env(), + number.DoubleValue() == static_cast(number)); } Value CreateEmptyNumber(const CallbackInfo& info) { @@ -93,7 +98,8 @@ Object InitBasicTypesNumber(Env env) { exports["operatorFloat"] = Function::New(env, OperatorFloat); exports["operatorDouble"] = Function::New(env, OperatorDouble); exports["createEmptyNumber"] = Function::New(env, CreateEmptyNumber); - exports["createNumberFromExistingValue"] = Function::New(env, CreateNumberFromExistingValue); + exports["createNumberFromExistingValue"] = + Function::New(env, CreateNumberFromExistingValue); return exports; } diff --git a/test/basic_types/number.js b/test/basic_types/number.js index a7c66bbc3..41e5ebdd7 100644 --- a/test/basic_types/number.js +++ b/test/basic_types/number.js @@ -1,12 +1,11 @@ +/* eslint-disable no-lone-blocks */ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { +function test (binding) { const MIN_INT32 = -2147483648; const MAX_INT32 = 2147483647; const MIN_UINT32 = 0; @@ -18,9 +17,9 @@ function test(binding) { const MIN_DOUBLE = binding.basic_types_number.minDouble(); const MAX_DOUBLE = binding.basic_types_number.maxDouble(); - function randomRangeTestForInteger(min, max, converter) { - for (let i = min; i < max; i+= Math.floor(Math.random() * max / 100)) { - assert.strictEqual(i, converter(i)); + function randomRangeTestForInteger (min, max, converter) { + for (let i = min; i < max; i += Math.floor(Math.random() * max / 100)) { + assert.strictEqual(i, converter(i)); } } @@ -98,19 +97,19 @@ function test(binding) { // Construction test { -    assert.strictEqual(binding.basic_types_number.createEmptyNumber(), true); -    randomRangeTestForInteger(MIN_INT32, MAX_INT32, binding.basic_types_number.createNumberFromExistingValue); -    assert.strictEqual(MIN_INT32, binding.basic_types_number.createNumberFromExistingValue(MIN_INT32)); -    assert.strictEqual(MAX_INT32, binding.basic_types_number.createNumberFromExistingValue(MAX_INT32)); -    randomRangeTestForInteger(MIN_UINT32, MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue); -    assert.strictEqual(MIN_UINT32, binding.basic_types_number.createNumberFromExistingValue(MIN_UINT32)); -    assert.strictEqual(MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue(MAX_UINT32)); -    randomRangeTestForInteger(MIN_INT64, MAX_INT64, binding.basic_types_number.createNumberFromExistingValue); -    assert.strictEqual(MIN_INT64, binding.basic_types_number.createNumberFromExistingValue(MIN_INT64)); -    assert.strictEqual(MAX_INT64, binding.basic_types_number.createNumberFromExistingValue(MAX_INT64)); -    assert.strictEqual(MIN_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MIN_FLOAT)); -    assert.strictEqual(MAX_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MAX_FLOAT)); -    assert.strictEqual(MIN_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MIN_DOUBLE)); -    assert.strictEqual(MAX_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MAX_DOUBLE)); + assert.strictEqual(binding.basic_types_number.createEmptyNumber(), true); + randomRangeTestForInteger(MIN_INT32, MAX_INT32, binding.basic_types_number.createNumberFromExistingValue); + assert.strictEqual(MIN_INT32, binding.basic_types_number.createNumberFromExistingValue(MIN_INT32)); + assert.strictEqual(MAX_INT32, binding.basic_types_number.createNumberFromExistingValue(MAX_INT32)); + randomRangeTestForInteger(MIN_UINT32, MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue); + assert.strictEqual(MIN_UINT32, binding.basic_types_number.createNumberFromExistingValue(MIN_UINT32)); + assert.strictEqual(MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue(MAX_UINT32)); + randomRangeTestForInteger(MIN_INT64, MAX_INT64, binding.basic_types_number.createNumberFromExistingValue); + assert.strictEqual(MIN_INT64, binding.basic_types_number.createNumberFromExistingValue(MIN_INT64)); + assert.strictEqual(MAX_INT64, binding.basic_types_number.createNumberFromExistingValue(MAX_INT64)); + assert.strictEqual(MIN_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MIN_FLOAT)); + assert.strictEqual(MAX_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MAX_FLOAT)); + assert.strictEqual(MIN_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MIN_DOUBLE)); + assert.strictEqual(MAX_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MAX_DOUBLE)); } } diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index d18f8f30f..7ec3b7e04 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -11,7 +12,44 @@ Value CreateExternal(const CallbackInfo& info) { return External::New(info.Env(), &testData); } -} // end anonymous namespace +} // end anonymous namespace + +static Value StrictlyEquals(const CallbackInfo& info) { + bool strictlyEquals = info[0].StrictEquals(info[1]); + return Boolean::New(info.Env(), strictlyEquals); +} + +// tests the '==' overload +static Value StrictEqualsOverload(const CallbackInfo& info) { + bool strictlyEquals = info[0] == info[1]; + return Boolean::New(info.Env(), strictlyEquals); +} + +// tests the '!=' overload +static Value StrictlyNotEqualsOverload(const CallbackInfo& info) { + bool strictlyEquals = info[0] != info[1]; + return Boolean::New(info.Env(), strictlyEquals); +} + +static Value ValueReturnsCorrectEnv(const CallbackInfo& info) { + Value testValue = CreateExternal(info); + return Boolean::New(info.Env(), testValue.Env() == info.Env()); +} + +static Value EmptyValueReturnNullPtrOnCast(const CallbackInfo& info) { + Value emptyValue; + bool isNullPtr = static_cast(emptyValue) == nullptr; + return Boolean::New(info.Env(), isNullPtr); +} + +static Value NonEmptyValueReturnValOnCast(const CallbackInfo& info) { + Value boolValue = Value::From(info.Env(), true); + return Boolean::New(info.Env(), static_cast(boolValue)); +} + +static Value CreateNonEmptyValue(const CallbackInfo& info) { + return Napi::Value(info.Env(), String::New(info.Env(), "non_empty_val")); +} static Value IsEmpty(const CallbackInfo& info) { Value value; @@ -75,19 +113,24 @@ static Value IsExternal(const CallbackInfo& info) { } static Value ToBoolean(const CallbackInfo& info) { - return info[0].ToBoolean(); + return MaybeUnwrap(info[0].ToBoolean()); } static Value ToNumber(const CallbackInfo& info) { - return info[0].ToNumber(); + return MaybeUnwrap(info[0].ToNumber()); } static Value ToString(const CallbackInfo& info) { - return info[0].ToString(); + return MaybeUnwrap(info[0].ToString()); } static Value ToObject(const CallbackInfo& info) { - return info[0].ToObject(); + return MaybeUnwrap(info[0].ToObject()); +} + +static Value AccessProp(const CallbackInfo& info) { + Object obj = MaybeUnwrap(info[0].ToObject()); + return obj[info[1]].AsValue(); } Object InitBasicTypesValue(Env env) { @@ -112,7 +155,22 @@ Object InitBasicTypesValue(Env env) { exports["toNumber"] = Function::New(env, ToNumber); exports["toString"] = Function::New(env, ToString); exports["toObject"] = Function::New(env, ToObject); + exports["accessProp"] = Function::New(env, AccessProp); + + exports["strictlyEquals"] = Function::New(env, StrictlyEquals); + exports["strictlyEqualsOverload"] = Function::New(env, StrictEqualsOverload); + exports["strictlyNotEqualsOverload"] = + Function::New(env, StrictlyNotEqualsOverload); + + exports["assertValueReturnsCorrectEnv"] = + Function::New(env, ValueReturnsCorrectEnv); + + exports["assertEmptyValReturnNullPtrOnCast"] = + Function::New(env, EmptyValueReturnNullPtrOnCast); + exports["assertNonEmptyReturnValOnCast"] = + Function::New(env, NonEmptyValueReturnValOnCast); + exports["createNonEmptyValue"] = Function::New(env, CreateNonEmptyValue); exports["createExternal"] = Function::New(env, CreateExternal); return exports; diff --git a/test/basic_types/value.js b/test/basic_types/value.js index bef7e44e8..adf73d39d 100644 --- a/test/basic_types/value.js +++ b/test/basic_types/value.js @@ -1,38 +1,30 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { +function test (binding) { const externalValue = binding.basic_types_value.createExternal(); - function isObject(value) { + function isObject (value) { return (typeof value === 'object' && value !== externalValue) || (typeof value === 'function'); } - function detailedTypeOf(value) { + function detailedTypeOf (value) { const type = typeof value; - if (type !== 'object') - return type; + if (type !== 'object') { return type; } - if (value === null) - return 'null'; + if (value === null) { return 'null'; } - if (Array.isArray(value)) - return 'array'; + if (Array.isArray(value)) { return 'array'; } - if (value === externalValue) - return 'external'; + if (value === externalValue) { return 'external'; } - if (!value.constructor) - return type; + if (!value.constructor) { return type; } - if (value instanceof ArrayBuffer) - return 'arraybuffer'; + if (value instanceof ArrayBuffer) { return 'arraybuffer'; } if (ArrayBuffer.isView(value)) { if (value instanceof DataView) { @@ -42,13 +34,12 @@ function test(binding) { } } - if (value instanceof Promise) - return 'promise'; + if (value instanceof Promise) { return 'promise'; } return 'object'; } - function typeCheckerTest(typeChecker, expectedType) { + function typeCheckerTest (typeChecker, expectedType) { const testValueList = [ undefined, null, @@ -60,7 +51,7 @@ function test(binding) { new ArrayBuffer(10), new Int32Array(new ArrayBuffer(12)), {}, - function() {}, + function () {}, new Promise((resolve, reject) => {}), new DataView(new ArrayBuffer(12)), externalValue @@ -75,7 +66,7 @@ function test(binding) { }); } - function typeConverterTest(typeConverter, expectedType) { + function typeConverterTest (typeConverter, expectedType) { const testValueList = [ true, false, @@ -86,7 +77,7 @@ function test(binding) { new ArrayBuffer(10), new Int32Array(new ArrayBuffer(12)), {}, - function() {}, + function () {}, new Promise((resolve, reject) => {}) ]; @@ -102,8 +93,53 @@ function test(binding) { }); } + function assertValueStrictlyEqual (value) { + const newValue = value.createNonEmptyValue(); + assert(value.strictlyEquals(newValue, newValue)); + assert(value.strictlyEqualsOverload(newValue, newValue)); + } + + function assertValueStrictlyNonEqual (value) { + const valueA = value.createNonEmptyValue(); + const valueB = value.createExternal(); + assert(value.strictlyNotEqualsOverload(valueA, valueB)); + } + + function assertValueReturnsCorrectEnv (value) { + assert(value.assertValueReturnsCorrectEnv()); + } + + function assertEmptyValueNullPtrOnCast (value) { + assert(value.assertEmptyValReturnNullPtrOnCast()); + } + + function assertNonEmptyReturnValOnCast (value) { + assert(value.assertNonEmptyReturnValOnCast()); + } + + function accessPropTest (value) { + const testObject = { key: '123' }; + const testSymbol = Symbol('123'); + const testNumber = 123; + const destObj = { + testObject, + testSymbol, + [testNumber]: testNumber + }; + assert.strictEqual(value.accessProp(destObj, 'testObject'), testObject); + assert.strictEqual(value.accessProp(destObj, 'testSymbol'), testSymbol); + assert.strictEqual(value.accessProp(destObj, testNumber), testNumber); + assert.strictEqual(value.accessProp(destObj, 'invalidKey'), undefined); + } + const value = binding.basic_types_value; + assertValueStrictlyEqual(value); + assertValueStrictlyNonEqual(value); + assertValueReturnsCorrectEnv(value); + assertEmptyValueNullPtrOnCast(value); + assertNonEmptyReturnValOnCast(value); + typeCheckerTest(value.isUndefined, 'undefined'); typeCheckerTest(value.isNull, 'null'); typeCheckerTest(value.isBoolean, 'boolean'); @@ -132,4 +168,6 @@ function test(binding) { assert.strictEqual(value.toString(null), 'null'); typeConverterTest(value.toObject, Object); + + accessPropTest(value); } diff --git a/test/bigint.cc b/test/bigint.cc index 1f89db84a..4faccddfb 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -1,8 +1,9 @@ #if (NAPI_VERSION > 5) -#define NAPI_EXPERIMENTAL #include "napi.h" +#include "test_helper.h" + using namespace Napi; namespace { @@ -11,7 +12,7 @@ Value IsLossless(const CallbackInfo& info) { Env env = info.Env(); BigInt big = info[0].As(); - bool is_signed = info[1].ToBoolean().Value(); + bool is_signed = MaybeUnwrap(info[1].ToBoolean()).Value(); bool lossless; if (is_signed) { @@ -23,6 +24,14 @@ Value IsLossless(const CallbackInfo& info) { return Boolean::New(env, lossless); } +Value IsBigInt(const CallbackInfo& info) { + Env env = info.Env(); + + BigInt big = info[0].As(); + + return Boolean::New(env, big.IsBigInt()); +} + Value TestInt64(const CallbackInfo& info) { bool lossless; int64_t input = info[0].As().Int64Value(&lossless); @@ -44,12 +53,13 @@ Value TestWords(const CallbackInfo& info) { int sign_bit; size_t word_count = 10; - uint64_t words[10]; + uint64_t words[10] = {0}; big.ToWords(&sign_bit, &word_count, words); if (word_count != expected_word_count) { - Error::New(info.Env(), "word count did not match").ThrowAsJavaScriptException(); + Error::New(info.Env(), "word count did not match") + .ThrowAsJavaScriptException(); return BigInt(); } @@ -59,7 +69,7 @@ Value TestWords(const CallbackInfo& info) { Value TestTooBigBigInt(const CallbackInfo& info) { int sign_bit = 0; size_t word_count = SIZE_MAX; - uint64_t words[10]; + uint64_t words[10] = {0}; return BigInt::New(info.Env(), sign_bit, word_count, words); } @@ -69,6 +79,7 @@ Value TestTooBigBigInt(const CallbackInfo& info) { Object InitBigInt(Env env) { Object exports = Object::New(env); exports["IsLossless"] = Function::New(env, IsLossless); + exports["IsBigInt"] = Function::New(env, IsBigInt); exports["TestInt64"] = Function::New(env, TestInt64); exports["TestUint64"] = Function::New(env, TestUint64); exports["TestWords"] = Function::New(env, TestWords); diff --git a/test/bigint.js b/test/bigint.js index 0af867b43..c30e525d4 100644 --- a/test/bigint.js +++ b/test/bigint.js @@ -1,18 +1,17 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { const { TestInt64, TestUint64, TestWords, IsLossless, - TestTooBigBigInt, + IsBigInt, + TestTooBigBigInt } = binding.bigint; [ @@ -26,7 +25,7 @@ function test(binding) { 986583n, -976675n, 98765432213456789876546896323445679887645323232436587988766545658n, - -4350987086545760976737453646576078997096876957864353245245769809n, + -4350987086545760976737453646576078997096876957864353245245769809n ].forEach((num) => { if (num > -(2n ** 63n) && num < 2n ** 63n) { assert.strictEqual(TestInt64(num), num); @@ -42,11 +41,13 @@ function test(binding) { assert.strictEqual(IsLossless(num, false), false); } + assert.strictEqual(IsBigInt(num), true); + assert.strictEqual(num, TestWords(num)); }); assert.throws(TestTooBigBigInt, { name: /^(RangeError|Error)$/, - message: /^(Maximum BigInt size exceeded|Invalid argument)$/, + message: /^(Maximum BigInt size exceeded|Invalid argument)$/ }); } diff --git a/test/binding-swallowexcept.cc b/test/binding-swallowexcept.cc new file mode 100644 index 000000000..febc7db66 --- /dev/null +++ b/test/binding-swallowexcept.cc @@ -0,0 +1,12 @@ +#include "napi.h" + +using namespace Napi; + +Object InitError(Env env); + +Object Init(Env env, Object exports) { + exports.Set("error", InitError(env)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/test/binding.cc b/test/binding.cc index 03661da35..fa651cc13 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -22,49 +22,72 @@ Object InitBasicTypesValue(Env env); Object InitBigInt(Env env); #endif Object InitBuffer(Env env); +Object InitBufferNoExternal(Env env); #if (NAPI_VERSION > 2) Object InitCallbackScope(Env env); #endif #if (NAPI_VERSION > 4) Object InitDate(Env env); #endif +Object InitCallbackInfo(Env env); Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); +Object InitEnvCleanup(Env env); +Object InitErrorHandlingPrim(Env env); Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); +Object InitFunctionReference(Env env); Object InitHandleScope(Env env); +Object InitMovableCallbacks(Env env); Object InitMemoryManagement(Env env); Object InitName(Env env); Object InitObject(Env env); #ifndef NODE_ADDON_API_DISABLE_DEPRECATED Object InitObjectDeprecated(Env env); -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); Object InitRunScript(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionCtx(Env env); +Object InitThreadSafeFunctionException(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); Object InitTypedThreadSafeFunctionCtx(Env env); +Object InitTypedThreadSafeFunctionException(Env env); Object InitTypedThreadSafeFunctionExistingTsfn(Env env); Object InitTypedThreadSafeFunctionPtr(Env env); Object InitTypedThreadSafeFunctionSum(Env env); Object InitTypedThreadSafeFunctionUnref(Env env); Object InitTypedThreadSafeFunction(Env env); #endif +Object InitSharedArrayBuffer(Env env); +Object InitSymbol(Env env); Object InitTypedArray(Env env); +Object InitGlobalObject(Env env); Object InitObjectWrap(Env env); Object InitObjectWrapConstructorException(Env env); +Object InitObjectWrapFunction(Env env); Object InitObjectWrapRemoveWrap(Env env); Object InitObjectWrapMultipleInheritance(Env env); Object InitObjectReference(Env env); Object InitReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); +#if (NAPI_VERSION > 7) +Object InitObjectFreezeSeal(Env env); +Object InitTypeTaggable(Env env); +#endif +#if (NAPI_VERSION > 8) +Object InitEnvMiscellaneous(Env env); +#endif +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +Object InitMaybeCheck(Env env); +#endif +Object InitFinalizerOrder(Env env); Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -77,6 +100,7 @@ Object Init(Env env, Object exports) { exports.Set("asyncprogressqueueworker", InitAsyncProgressQueueWorker(env)); exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); #endif + exports.Set("globalObject", InitGlobalObject(env)); exports.Set("asyncworker", InitAsyncWorker(env)); exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env)); exports.Set("basic_types_array", InitBasicTypesArray(env)); @@ -90,33 +114,48 @@ Object Init(Env env, Object exports) { exports.Set("date", InitDate(env)); #endif exports.Set("buffer", InitBuffer(env)); + exports.Set("bufferNoExternal", InitBufferNoExternal(env)); #if (NAPI_VERSION > 2) exports.Set("callbackscope", InitCallbackScope(env)); #endif + exports.Set("callbackInfo", InitCallbackInfo(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); exports.Set("dataview_read_write", InitDataViewReadWrite(env)); +#if (NAPI_VERSION > 2) + exports.Set("env_cleanup", InitEnvCleanup(env)); +#endif exports.Set("error", InitError(env)); + exports.Set("errorHandlingPrim", InitErrorHandlingPrim(env)); exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); + exports.Set("functionreference", InitFunctionReference(env)); exports.Set("name", InitName(env)); exports.Set("handlescope", InitHandleScope(env)); + exports.Set("movable_callbacks", InitMovableCallbacks(env)); exports.Set("memory_management", InitMemoryManagement(env)); exports.Set("object", InitObject(env)); #ifndef NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("object_deprecated", InitObjectDeprecated(env)); -#endif // !NODE_ADDON_API_DISABLE_DEPRECATED +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); exports.Set("run_script", InitRunScript(env)); + exports.Set("symbol", InitSymbol(env)); + exports.Set("sharedarraybuffer", InitSharedArrayBuffer(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); - exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); + exports.Set("threadsafe_function_exception", + InitThreadSafeFunctionException(env)); + exports.Set("threadsafe_function_existing_tsfn", + InitThreadSafeFunctionExistingTsfn(env)); exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); - exports.Set("threadsafe_function", InitTypedThreadSafeFunction(env)); + exports.Set("threadsafe_function", InitThreadSafeFunction(env)); exports.Set("typed_threadsafe_function_ctx", InitTypedThreadSafeFunctionCtx(env)); + exports.Set("typed_threadsafe_function_exception", + InitTypedThreadSafeFunctionException(env)); exports.Set("typed_threadsafe_function_existing_tsfn", InitTypedThreadSafeFunctionExistingTsfn(env)); exports.Set("typed_threadsafe_function_ptr", @@ -130,13 +169,39 @@ Object Init(Env env, Object exports) { exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectwrapConstructorException", - InitObjectWrapConstructorException(env)); + InitObjectWrapConstructorException(env)); + exports.Set("objectwrap_function", InitObjectWrapFunction(env)); exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env)); - exports.Set("objectwrap_multiple_inheritance", InitObjectWrapMultipleInheritance(env)); + exports.Set("objectwrap_multiple_inheritance", + InitObjectWrapMultipleInheritance(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("reference", InitReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); +#if (NAPI_VERSION > 7) + exports.Set("object_freeze_seal", InitObjectFreezeSeal(env)); + exports.Set("type_taggable", InitTypeTaggable(env)); +#endif +#if (NAPI_VERSION > 8) + exports.Set("env_misc", InitEnvMiscellaneous(env)); +#endif + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + exports.Set("maybe_check", InitMaybeCheck(env)); +#endif + + exports.Set("finalizer_order", InitFinalizerOrder(env)); + + exports.Set( + "isExperimental", + Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL)); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, true)); +#else + exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, false)); +#endif + return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 90dee4565..9ff334b64 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,48 +1,71 @@ { 'target_defaults': { 'includes': ['../common.gypi'], - 'sources': [ + 'include_dirs': ['./common'], + 'variables': { + 'build_sources': [ 'addon.cc', 'addon_data.cc', - 'arraybuffer.cc', - 'asynccontext.cc', - 'asyncprogressqueueworker.cc', - 'asyncprogressworker.cc', - 'asyncworker.cc', - 'asyncworker-persistent.cc', + 'array_buffer.cc', + 'async_context.cc', + 'async_progress_queue_worker.cc', + 'async_progress_worker.cc', + 'async_worker.cc', + 'async_worker_persistent.cc', 'basic_types/array.cc', 'basic_types/boolean.cc', 'basic_types/number.cc', 'basic_types/value.cc', 'bigint.cc', + 'callbackInfo.cc', 'date.cc', 'binding.cc', + 'buffer_no_external.cc', 'buffer.cc', 'callbackscope.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', + 'env_cleanup.cc', + 'env_misc.cc', 'error.cc', + 'error_handling_for_primitives.cc', 'external.cc', + 'finalizer_order.cc', 'function.cc', + 'function_reference.cc', 'handlescope.cc', + 'maybe/check.cc', + 'movable_callbacks.cc', 'memory_management.cc', 'name.cc', + 'globalObject/global_object_delete_property.cc', + 'globalObject/global_object_has_own_property.cc', + 'globalObject/global_object_set_property.cc', + 'globalObject/global_object_get_property.cc', + 'globalObject/global_object.cc', 'object/delete_property.cc', 'object/finalizer.cc', 'object/get_property.cc', 'object/has_own_property.cc', 'object/has_property.cc', 'object/object.cc', + 'object/object_freeze_seal.cc', 'object/set_property.cc', + 'object/subscript_operator.cc', 'promise.cc', 'run_script.cc', + 'shared_array_buffer.cc', + 'symbol.cc', 'threadsafe_function/threadsafe_function_ctx.cc', + 'threadsafe_function/threadsafe_function_exception.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function_sum.cc', 'threadsafe_function/threadsafe_function_unref.cc', 'threadsafe_function/threadsafe_function.cc', + 'type_taggable.cc', 'typed_threadsafe_function/typed_threadsafe_function_ctx.cc', + 'typed_threadsafe_function/typed_threadsafe_function_exception.cc', 'typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc', 'typed_threadsafe_function/typed_threadsafe_function_ptr.cc', 'typed_threadsafe_function/typed_threadsafe_function_sum.cc', @@ -51,27 +74,88 @@ 'typedarray.cc', 'objectwrap.cc', 'objectwrap_constructor_exception.cc', - 'objectwrap-removewrap.cc', + 'objectwrap_function.cc', + 'objectwrap_removewrap.cc', 'objectwrap_multiple_inheritance.cc', - 'objectreference.cc', + 'object_reference.cc', 'reference.cc', 'version_management.cc', 'thunking_manual.cc', ], + 'build_sources_swallowexcept': [ + 'binding-swallowexcept.cc', + 'error.cc', + ], + 'build_sources_except_all': [ + 'except_all.cc', + ], + 'build_sources_type_check': [ + 'value_type_cast.cc' + ], + 'want_coverage': '@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] + }, + { + 'target_name': 'binding_except_all', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], + 'sources': [ '>@(build_sources_except_all)'] }, { 'target_name': 'binding_noexcept', - 'includes': ['../noexcept.gypi'] + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], + 'sources': ['>@(build_sources)'] + }, + { + 'target_name': 'binding_noexcept_maybe', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_maybe'], + 'sources': ['>@(build_sources)'], + }, + { + 'target_name': 'binding_swallowexcept', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], + 'sources': [ '>@(build_sources_swallowexcept)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'] + }, + { + 'target_name': 'binding_swallowexcept_noexcept', + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], + 'sources': ['>@(build_sources_swallowexcept)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'] + }, + { + 'target_name': 'binding_type_check', + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], + 'sources': ['>@(build_sources_type_check)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] + }, + { + 'target_name': 'binding_custom_namespace', + 'dependencies': ['../node_addon_api.gyp:node_addon_api'], + 'sources': ['>@(build_sources)'], + 'defines': ['NAPI_CPP_CUSTOM_NAMESPACE=cstm'] }, ], } diff --git a/test/buffer.cc b/test/buffer.cc index c92c6fbf6..c10300dc7 100644 --- a/test/buffer.cc +++ b/test/buffer.cc @@ -1,35 +1,22 @@ +#include "buffer.h" #include "napi.h" using namespace Napi; -namespace { - -const size_t testLength = 4; +namespace test_buffer { uint16_t testData[testLength]; int finalizeCount = 0; +} // namespace test_buffer -template -void InitData(T* data, size_t length) { - for (size_t i = 0; i < length; i++) { - data[i] = static_cast(i); - } -} - -template -bool VerifyData(T* data, size_t length) { - for (size_t i = 0; i < length; i++) { - if (data[i] != static_cast(i)) { - return false; - } - } - return true; -} +using namespace test_buffer; +namespace { Value CreateBuffer(const CallbackInfo& info) { Buffer buffer = Buffer::New(info.Env(), testLength); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } @@ -40,18 +27,18 @@ Value CreateBuffer(const CallbackInfo& info) { Value CreateExternalBuffer(const CallbackInfo& info) { finalizeCount = 0; - Buffer buffer = Buffer::New( - info.Env(), - testData, - testLength); + Buffer buffer = + Buffer::New(info.Env(), testData, testLength); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != testData) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -65,21 +52,20 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { uint16_t* data = new uint16_t[testLength]; Buffer buffer = Buffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, uint16_t* finalizeData) { - delete[] finalizeData; - finalizeCount++; - }); + info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) { + delete[] finalizeData; + finalizeCount++; + }); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -94,22 +80,24 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { char* hint = nullptr; Buffer buffer = Buffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) { - delete[] finalizeData; - finalizeCount++; - }, - hint); + info.Env(), + data, + testLength, + [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) { + delete[] finalizeData; + finalizeCount++; + }, + hint); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -120,51 +108,59 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { Value CreateBufferCopy(const CallbackInfo& info) { InitData(testData, testLength); - Buffer buffer = Buffer::Copy( - info.Env(), testData, testLength); + Buffer buffer = + Buffer::Copy(info.Env(), testData, testLength); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() == testData) { - Error::New(info.Env(), "Copy should have different memory.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Copy should have different memory.") + .ThrowAsJavaScriptException(); return Value(); } if (!VerifyData(buffer.Data(), buffer.Length())) { - Error::New(info.Env(), "Copy data is incorrect.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Copy data is incorrect.") + .ThrowAsJavaScriptException(); return Value(); } return buffer; } +#include "buffer_new_or_copy-inl.h" + void CheckBuffer(const CallbackInfo& info) { if (!info[0].IsBuffer()) { - Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A buffer was expected.") + .ThrowAsJavaScriptException(); return; } Buffer buffer = info[0].As>(); if (buffer.Length() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return; } if (!VerifyData(buffer.Data(), testLength)) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return; } } Value GetFinalizeCount(const CallbackInfo& info) { - return Number::New(info.Env(), finalizeCount); + return Number::New(info.Env(), finalizeCount); } -} // end anonymous namespace +} // end anonymous namespace Object InitBuffer(Env env) { Object exports = Object::New(env); @@ -172,9 +168,15 @@ Object InitBuffer(Env env) { exports["createBuffer"] = Function::New(env, CreateBuffer); exports["createExternalBuffer"] = Function::New(env, CreateExternalBuffer); exports["createExternalBufferWithFinalize"] = - Function::New(env, CreateExternalBufferWithFinalize); + Function::New(env, CreateExternalBufferWithFinalize); exports["createExternalBufferWithFinalizeHint"] = - Function::New(env, CreateExternalBufferWithFinalizeHint); + Function::New(env, CreateExternalBufferWithFinalizeHint); + exports["createOrCopyExternalBuffer"] = + Function::New(env, CreateOrCopyExternalBuffer); + exports["createOrCopyExternalBufferWithFinalize"] = + Function::New(env, CreateOrCopyExternalBufferWithFinalize); + exports["createOrCopyExternalBufferWithFinalizeHint"] = + Function::New(env, CreateOrCopyExternalBufferWithFinalizeHint); exports["createBufferCopy"] = Function::New(env, CreateBufferCopy); exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); diff --git a/test/buffer.h b/test/buffer.h new file mode 100644 index 000000000..ed2a71771 --- /dev/null +++ b/test/buffer.h @@ -0,0 +1,26 @@ +#include +#include + +namespace test_buffer { + +const size_t testLength = 4; +extern uint16_t testData[testLength]; +extern int finalizeCount; + +template +void InitData(T* data, size_t length) { + for (size_t i = 0; i < length; i++) { + data[i] = static_cast(i); + } +} + +template +bool VerifyData(T* data, size_t length) { + for (size_t i = 0; i < length; i++) { + if (data[i] != static_cast(i)) { + return false; + } + } + return true; +} +} // namespace test_buffer diff --git a/test/buffer.js b/test/buffer.js index ff17d30e7..3f49201a7 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -1,13 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -const safeBuffer = require('safe-buffer'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { return testUtil.runGCTests([ 'Internal Buffer', () => { @@ -15,7 +13,7 @@ function test(binding) { binding.buffer.checkBuffer(test); assert.ok(test instanceof Buffer); - const test2 = safeBuffer.Buffer.alloc(test.length); + const test2 = Buffer.alloc(test.length); test.copy(test2); binding.buffer.checkBuffer(test2); }, @@ -35,8 +33,8 @@ function test(binding) { assert.strictEqual(0, binding.buffer.getFinalizeCount()); }, () => { - global.gc(); - assert.strictEqual(0, binding.buffer.getFinalizeCount()); + global.gc(); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); }, 'External Buffer with finalizer', @@ -47,24 +45,106 @@ function test(binding) { assert.strictEqual(0, binding.buffer.getFinalizeCount()); }, () => { - global.gc(); + global.gc(); }, () => { - assert.strictEqual(1, binding.buffer.getFinalizeCount()); + assert.strictEqual(1, binding.buffer.getFinalizeCount()); }, 'External Buffer with finalizer hint', () => { - const test = binding.buffer.createExternalBufferWithFinalizeHint(); - binding.buffer.checkBuffer(test); - assert.ok(test instanceof Buffer); - assert.strictEqual(0, binding.buffer.getFinalizeCount()); + const test = binding.buffer.createExternalBufferWithFinalizeHint(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + }, + () => { + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + + 'Create or Copy External Buffer', + () => { + const test = binding.buffer.createOrCopyExternalBuffer(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + + 'Create or Copy External Buffer with finalizer', + () => { + const test = binding.buffer.createOrCopyExternalBufferWithFinalize(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + }, + () => { + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + + 'Create or Copy External Buffer with finalizer hint', + () => { + const test = binding.buffer.createOrCopyExternalBufferWithFinalizeHint(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + }, + () => { + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + + 'Create or Copy External Buffer when NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED defined', + () => { + const test = binding.bufferNoExternal.createOrCopyExternalBuffer(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + + 'Create or Copy External Buffer with finalizer when NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED defined', + () => { + const test = binding.bufferNoExternal.createOrCopyExternalBufferWithFinalize(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + // finalizer should have been called when the buffer was created. + assert.strictEqual(1, binding.buffer.getFinalizeCount()); }, () => { - global.gc(); + global.gc(); }, () => { - assert.strictEqual(1, binding.buffer.getFinalizeCount()); + assert.strictEqual(1, binding.buffer.getFinalizeCount()); }, + + 'Create or Copy External Buffer with finalizer hint when NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED defined', + () => { + const test = binding.bufferNoExternal.createOrCopyExternalBufferWithFinalizeHint(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + // finalizer should have been called when the buffer was created. + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + }, + () => { + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + } ]); } diff --git a/test/buffer_new_or_copy-inl.h b/test/buffer_new_or_copy-inl.h new file mode 100644 index 000000000..4d68fbc91 --- /dev/null +++ b/test/buffer_new_or_copy-inl.h @@ -0,0 +1,68 @@ +// Same tests on when NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED is defined or not +// defined. + +Value CreateOrCopyExternalBuffer(const CallbackInfo& info) { + finalizeCount = 0; + + InitData(testData, testLength); + Buffer buffer = + Buffer::NewOrCopy(info.Env(), testData, testLength); + + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); + return Value(); + } + + VerifyData(buffer.Data(), testLength); + return buffer; +} + +Value CreateOrCopyExternalBufferWithFinalize(const CallbackInfo& info) { + finalizeCount = 0; + + uint16_t* data = new uint16_t[testLength]; + InitData(data, testLength); + + Buffer buffer = Buffer::NewOrCopy( + info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) { + delete[] finalizeData; + finalizeCount++; + }); + + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); + return Value(); + } + + VerifyData(buffer.Data(), testLength); + return buffer; +} + +Value CreateOrCopyExternalBufferWithFinalizeHint(const CallbackInfo& info) { + finalizeCount = 0; + + uint16_t* data = new uint16_t[testLength]; + InitData(data, testLength); + + char* hint = nullptr; + Buffer buffer = Buffer::NewOrCopy( + info.Env(), + data, + testLength, + [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) { + delete[] finalizeData; + finalizeCount++; + }, + hint); + + if (buffer.Length() != testLength) { + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); + return Value(); + } + + VerifyData(buffer.Data(), testLength); + return buffer; +} diff --git a/test/buffer_no_external.cc b/test/buffer_no_external.cc new file mode 100644 index 000000000..11920bf1c --- /dev/null +++ b/test/buffer_no_external.cc @@ -0,0 +1,24 @@ +#define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +// Should compile without errors +#include "buffer.h" +#include "napi.h" + +using namespace Napi; +using namespace test_buffer; + +namespace { +#include "buffer_new_or_copy-inl.h" +} + +Object InitBufferNoExternal(Env env) { + Object exports = Object::New(env); + + exports["createOrCopyExternalBuffer"] = + Function::New(env, CreateOrCopyExternalBuffer); + exports["createOrCopyExternalBufferWithFinalize"] = + Function::New(env, CreateOrCopyExternalBufferWithFinalize); + exports["createOrCopyExternalBufferWithFinalizeHint"] = + Function::New(env, CreateOrCopyExternalBufferWithFinalizeHint); + + return exports; +} diff --git a/test/callbackInfo.cc b/test/callbackInfo.cc new file mode 100644 index 000000000..ad5f72356 --- /dev/null +++ b/test/callbackInfo.cc @@ -0,0 +1,27 @@ +#include +#include "napi.h" +using namespace Napi; + +struct TestCBInfoSetData { + static void Test(napi_env env, napi_callback_info info) { + Napi::CallbackInfo cbInfo(env, info); + int valuePointer = 1220202; + cbInfo.SetData(&valuePointer); + + int* placeHolder = static_cast(cbInfo.Data()); + assert(*(placeHolder) == valuePointer); + assert(placeHolder == &valuePointer); + } +}; + +void TestCallbackInfoSetData(const Napi::CallbackInfo& info) { + napi_callback_info cb_info = static_cast(info); + TestCBInfoSetData::Test(info.Env(), cb_info); +} + +Object InitCallbackInfo(Env env) { + Object exports = Object::New(env); + + exports["testCbSetData"] = Function::New(env, TestCallbackInfoSetData); + return exports; +} diff --git a/test/callbackInfo.js b/test/callbackInfo.js new file mode 100644 index 000000000..ea671a986 --- /dev/null +++ b/test/callbackInfo.js @@ -0,0 +1,9 @@ +'use strict'; + +const common = require('./common'); + +module.exports = common.runTest(test); + +async function test (binding) { + binding.callbackInfo.testCbSetData(); +} diff --git a/test/callbackscope.cc b/test/callbackscope.cc index 70b68fe60..9554a731a 100644 --- a/test/callbackscope.cc +++ b/test/callbackscope.cc @@ -1,8 +1,9 @@ +#include "assert.h" #include "napi.h" - using namespace Napi; #if (NAPI_VERSION > 2) + namespace { static void RunInCallbackScope(const CallbackInfo& info) { @@ -12,11 +13,27 @@ static void RunInCallbackScope(const CallbackInfo& info) { callback.Call({}); } -} // end anonymous namespace +static void RunInCallbackScopeFromExisting(const CallbackInfo& info) { + Function callback = info[0].As(); + Env env = info.Env(); + + AsyncContext ctx(env, "existing_callback_scope_test"); + napi_callback_scope scope; + napi_open_callback_scope(env, Object::New(env), ctx, &scope); + + CallbackScope existingScope(env, scope); + assert(existingScope.Env() == env); + + callback.Call({}); +} + +} // namespace Object InitCallbackScope(Env env) { Object exports = Object::New(env); exports["runInCallbackScope"] = Function::New(env, RunInCallbackScope); + exports["runInPreExistingCbScope"] = + Function::New(env, RunInCallbackScopeFromExisting); return exports; } #endif diff --git a/test/callbackscope.js b/test/callbackscope.js index 523bca462..cafb180ca 100644 --- a/test/callbackscope.js +++ b/test/callbackscope.js @@ -1,48 +1,49 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -const common = require('./common'); // we only check async hooks on 8.x an higher were // they are closer to working properly -const nodeVersion = process.versions.node.split('.')[0] -let async_hooks = undefined; -function checkAsyncHooks() { +const nodeVersion = process.versions.node.split('.')[0]; +let asyncHooks; +function checkAsyncHooks () { if (nodeVersion >= 8) { - if (async_hooks == undefined) { - async_hooks = require('async_hooks'); + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); } return true; } return false; } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { - if (!checkAsyncHooks()) - return; +function test (binding) { + if (!checkAsyncHooks()) { return; } let id; let insideHook = false; - async_hooks.createHook({ - init(asyncId, type, triggerAsyncId, resource) { - if (id === undefined && type === 'callback_scope_test') { + const hook = asyncHooks.createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && (type === 'callback_scope_test' || type === 'existing_callback_scope_test')) { id = asyncId; } }, - before(asyncId) { - if (asyncId === id) - insideHook = true; + before (asyncId) { + if (asyncId === id) { insideHook = true; } }, - after(asyncId) { - if (asyncId === id) - insideHook = false; + after (asyncId) { + if (asyncId === id) { insideHook = false; } } }).enable(); - binding.callbackscope.runInCallbackScope(function() { - assert(insideHook); + return new Promise(resolve => { + binding.callbackscope.runInCallbackScope(function () { + assert(insideHook); + binding.callbackscope.runInPreExistingCbScope(function () { + assert(insideHook); + hook.disable(); + resolve(); + }); + }); }); } diff --git a/test/child_processes/addon.js b/test/child_processes/addon.js new file mode 100644 index 000000000..33e80d798 --- /dev/null +++ b/test/child_processes/addon.js @@ -0,0 +1,11 @@ +'use strict'; +const assert = require('assert'); + +module.exports = { + workingCode: binding => { + const addon = binding.addon(); + assert.strictEqual(addon.increment(), 43); + assert.strictEqual(addon.increment(), 44); + assert.strictEqual(addon.subObject.decrement(), 43); + } +}; diff --git a/test/child_processes/addon_data.js b/test/child_processes/addon_data.js new file mode 100644 index 000000000..82d1aa317 --- /dev/null +++ b/test/child_processes/addon_data.js @@ -0,0 +1,24 @@ +'use strict'; + +const assert = require('assert'); + +// Make sure the instance data finalizer is called at process exit. If the hint +// is non-zero, it will be printed out by the child process. +const cleanupTest = (binding, hint) => { + binding.addon_data(hint).verbose = true; +}; + +module.exports = { + workingCode: binding => { + const addonData = binding.addon_data(0); + + // Make sure it is possible to get/set instance data. + assert.strictEqual(addonData.verbose.verbose, false); + addonData.verbose = true; + assert.strictEqual(addonData.verbose.verbose, true); + addonData.verbose = false; + assert.strictEqual(addonData.verbose.verbose, false); + }, + cleanupWithHint: binding => cleanupTest(binding, 42), + cleanupWithoutHint: binding => cleanupTest(binding, 0) +}; diff --git a/test/child_processes/objectwrap_function.js b/test/child_processes/objectwrap_function.js new file mode 100644 index 000000000..2ee83cb5e --- /dev/null +++ b/test/child_processes/objectwrap_function.js @@ -0,0 +1,22 @@ +'use strict'; + +const assert = require('assert'); +const testUtil = require('../testUtil'); + +module.exports = { + runTest: function (binding) { + return testUtil.runGCTests([ + 'objectwrap function', + () => { + const { FunctionTest } = binding.objectwrap_function(); + const newConstructed = new FunctionTest(); + const functionConstructed = FunctionTest(); + assert(newConstructed instanceof FunctionTest); + assert(functionConstructed instanceof FunctionTest); + assert.throws(() => (FunctionTest(true)), /an exception/); + }, + // Do one gc before returning. + () => {} + ]); + } +}; diff --git a/test/child_processes/threadsafe_function_exception.js b/test/child_processes/threadsafe_function_exception.js new file mode 100644 index 000000000..4fc63d7c8 --- /dev/null +++ b/test/child_processes/threadsafe_function_exception.js @@ -0,0 +1,33 @@ +'use strict'; + +const assert = require('assert'); +const common = require('../common'); + +module.exports = { + testCall: async binding => { + const { testCall } = binding.threadsafe_function_exception; + + await new Promise(resolve => { + process.once('uncaughtException', common.mustCall(err => { + assert.strictEqual(err.message, 'test'); + resolve(); + }, 1)); + + testCall(common.mustCall(() => { + throw new Error('test'); + }, 1)); + }); + }, + testCallWithNativeCallback: async binding => { + const { testCallWithNativeCallback } = binding.threadsafe_function_exception; + + await new Promise(resolve => { + process.once('uncaughtException', common.mustCall(err => { + assert.strictEqual(err.message, 'test-from-native'); + resolve(); + }, 1)); + + testCallWithNativeCallback(); + }); + } +}; diff --git a/test/child_processes/typed_threadsafe_function_exception.js b/test/child_processes/typed_threadsafe_function_exception.js new file mode 100644 index 000000000..5cbfab268 --- /dev/null +++ b/test/child_processes/typed_threadsafe_function_exception.js @@ -0,0 +1,19 @@ +'use strict'; + +const assert = require('assert'); +const common = require('../common'); + +module.exports = { + testCall: async binding => { + const { testCall } = binding.typed_threadsafe_function_exception; + + await new Promise(resolve => { + process.once('uncaughtException', common.mustCall(err => { + assert.strictEqual(err.message, 'test-from-native'); + resolve(); + }, 1)); + + testCall(); + }); + } +}; diff --git a/test/common/index.js b/test/common/index.js index 54139bb2d..2469151ef 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -1,15 +1,22 @@ /* Test helpers ported from test/common/index.js in Node.js project. */ 'use strict'; const assert = require('assert'); +const path = require('path'); +const { access } = require('node:fs/promises'); +const { spawn } = require('child_process'); +const { EOL } = require('os'); +const readline = require('readline'); + +const escapeBackslashes = (pathString) => pathString.split('\\').join('\\\\'); const noop = () => {}; const mustCallChecks = []; -function runCallChecks(exitCode) { +function runCallChecks (exitCode) { if (exitCode !== 0) return; - const failed = mustCallChecks.filter(function(context) { + const failed = mustCallChecks.filter(function (context) { if ('minimum' in context) { context.messageSegment = `at least ${context.minimum}`; return context.actual < context.minimum; @@ -19,25 +26,70 @@ function runCallChecks(exitCode) { } }); - failed.forEach(function(context) { + failed.forEach(function (context) { console.log('Mismatched %s function calls. Expected %s, actual %d.', - context.name, - context.messageSegment, - context.actual); - console.log(context.stack.split('\n').slice(2).join('\n')); + context.name, + context.messageSegment, + context.actual); + console.log(context.stack.split(EOL).slice(2).join(EOL)); }); if (failed.length) process.exit(1); } -exports.mustCall = function(fn, exact) { +exports.installAysncHooks = function (asyncResName) { + const asyncHooks = require('async_hooks'); + return new Promise((resolve, reject) => { + let id; + const events = []; + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let destroyed = false; + const hook = asyncHooks.createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === asyncResName) { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + destroyed = true; + } + } + }).enable(); + + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + }); +}; + +exports.mustCall = function (fn, exact) { return _mustCallInner(fn, exact, 'exact'); }; -exports.mustCallAtLeast = function(fn, minimum) { +exports.mustCallAtLeast = function (fn, minimum) { return _mustCallInner(fn, minimum, 'minimum'); }; -function _mustCallInner(fn, criteria, field) { +function _mustCallInner (fn, criteria, field) { if (typeof fn === 'number') { criteria = fn; fn = noop; @@ -48,8 +100,7 @@ function _mustCallInner(fn, criteria, field) { criteria = 1; } - if (typeof criteria !== 'number') - throw new TypeError(`Invalid ${field} value: ${criteria}`); + if (typeof criteria !== 'number') { throw new TypeError(`Invalid ${field} value: ${criteria}`); } const context = { [field]: criteria, @@ -63,14 +114,133 @@ function _mustCallInner(fn, criteria, field) { mustCallChecks.push(context); - return function() { + return function () { context.actual++; return fn.apply(this, arguments); }; } -exports.mustNotCall = function(msg) { - return function mustNotCall() { +exports.mustNotCall = function (msg) { + return function mustNotCall () { assert.fail(msg || 'function should not have been called'); }; }; + +const buildTypes = { + Release: 'Release', + Debug: 'Debug' +}; + +async function checkBuildType (buildType) { + try { + await access(path.join(path.resolve('./test/build'), buildType)); + return true; + } catch { + return false; + } +} + +async function whichBuildType () { + let buildType = 'Release'; + const envBuildType = process.env.NODE_API_BUILD_CONFIG || (process.env.npm_config_debug === 'true' ? 'Debug' : 'Release'); + if (envBuildType) { + if (Object.values(buildTypes).includes(envBuildType)) { + if (await checkBuildType(envBuildType)) { + buildType = envBuildType; + } else { + throw new Error(`The ${envBuildType} build doesn't exist.`); + } + } else { + throw new Error('Invalid value for NODE_API_BUILD_CONFIG environment variable. It should be set to Release or Debug.'); + } + } + return buildType; +} + +exports.whichBuildType = whichBuildType; + +exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { + buildType = buildType || await whichBuildType(); + const bindings = [ + path.join(buildPathRoot, `../build/${buildType}/binding.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); + + for (const item of bindings) { + await Promise.resolve(test(require(item), { bindingPath: item })) + .finally(exports.mustCall()); + } +}; + +exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { + buildType = buildType || await whichBuildType(); + + const bindings = [ + path.join(buildPathRoot, `../build/${buildType}/binding.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); + + for (const item of bindings) { + await test(item); + } +}; + +exports.runTestWithBuildType = async function (test, buildType) { + buildType = buildType || await whichBuildType(); + + await Promise.resolve(test(buildType)) + .finally(exports.mustCall()); +}; + +// Some tests have to run in their own process, otherwise they would interfere +// with each other. Such tests export a factory function rather than the test +// itself so as to avoid automatic instantiation, and therefore interference, +// in the main process. Two examples are addon and addon_data, both of which +// use Napi::Env::SetInstanceData(). This helper function provides a common +// approach for running such tests. +exports.runTestInChildProcess = function ({ suite, testName, expectedStderr, execArgv }) { + return exports.runTestWithBindingPath((bindingName) => { + return new Promise((resolve) => { + bindingName = escapeBackslashes(bindingName); + // Test suites are assumed to be located here. + const suitePath = escapeBackslashes(path.join(__dirname, '..', 'child_processes', suite)); + const child = spawn(process.execPath, [ + '--expose-gc', + ...(execArgv ?? []), + '-e', + `require('${suitePath}').${testName}(require('${bindingName}'))` + ]); + const resultOfProcess = { stderr: [] }; + + // Capture the exit code and signal. + child.on('close', (code, signal) => resolve(Object.assign(resultOfProcess, { code, signal }))); + + // Capture the stderr as an array of lines. + readline + .createInterface({ input: child.stderr }) + .on('line', (line) => { + resultOfProcess.stderr.push(line); + }); + }).then(actual => { + // Back up the stderr in case the assertion fails. + const fullStderr = actual.stderr.map(item => `from child process: ${item}`); + const expected = { stderr: expectedStderr, code: 0, signal: null }; + + if (!expectedStderr) { + // If we don't care about stderr, delete it. + delete actual.stderr; + delete expected.stderr; + } else { + // Otherwise we only care about expected lines in the actual stderr, so + // filter out everything else. + actual.stderr = actual.stderr.filter(line => expectedStderr.includes(line)); + } + + assert.deepStrictEqual(actual, expected, `Assertion for child process test ${suite}.${testName} failed:${EOL}` + fullStderr.join(EOL)); + }); + }); +}; diff --git a/test/common/test_helper.h b/test/common/test_helper.h new file mode 100644 index 000000000..88ab1e8e0 --- /dev/null +++ b/test/common/test_helper.h @@ -0,0 +1,71 @@ +#pragma once +#include "napi.h" + +namespace Napi { + +// Needs this here since the MaybeUnwrap() functions need to be in the +// same namespace as their arguments for C++ argument-dependent lookup +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Use this when a variable or parameter is unused in order to explicitly +// silence a compiler warning about that. +template +inline void USE(T&&) {} + +/** + * A test helper that converts MaybeOrValue to T by checking that + * MaybeOrValue is NOT an empty Maybe when NODE_ADDON_API_ENABLE_MAYBE is + * defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrap(MaybeOrValue maybe) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.Unwrap(); +#else + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return the default_value if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapOr(default_value); +#else + USE(default_value); + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return false if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Copying the value to out when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline bool MaybeUnwrapTo(MaybeOrValue maybe, T* out) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapTo(out); +#else + *out = maybe; + return true; +#endif +} + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi diff --git a/test/dataview/dataview.cc b/test/dataview/dataview.cc index f055d95f1..cbd5933e7 100644 --- a/test/dataview/dataview.cc +++ b/test/dataview/dataview.cc @@ -2,24 +2,51 @@ using namespace Napi; -static Value CreateDataView1(const CallbackInfo& info) { +static Value CreateDataView(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); return DataView::New(info.Env(), arrayBuffer); } -static Value CreateDataView2(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffset(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset); } -static Value CreateDataView3(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffsetAndByteLength( + const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); size_t byteLength = info[2].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +static Value CreateDataViewOnSharedArrayBuffer(const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + return DataView::New(info.Env(), arrayBuffer); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffset( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + size_t byteLength = info[2].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); +} +#endif + +static Value GetBuffer(const CallbackInfo& info) { + return info[0].As().Buffer(); +} + static Value GetArrayBuffer(const CallbackInfo& info) { return info[0].As().ArrayBuffer(); } @@ -37,10 +64,24 @@ static Value GetByteLength(const CallbackInfo& info) { Object InitDataView(Env env) { Object exports = Object::New(env); - exports["createDataView1"] = Function::New(env, CreateDataView1); - exports["createDataView2"] = Function::New(env, CreateDataView2); - exports["createDataView3"] = Function::New(env, CreateDataView3); + exports["createDataView"] = Function::New(env, CreateDataView); + exports["createDataViewWithByteOffset"] = + Function::New(env, CreateDataViewWithByteOffset); + exports["createDataViewWithByteOffsetAndByteLength"] = + Function::New(env, CreateDataViewWithByteOffsetAndByteLength); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createDataViewOnSharedArrayBuffer"] = + Function::New(env, CreateDataViewOnSharedArrayBuffer); + exports["createDataViewOnSharedArrayBufferWithByteOffset"] = + Function::New(env, CreateDataViewOnSharedArrayBufferWithByteOffset); + exports["createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength"] = + Function::New( + env, CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength); +#endif + exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer); + exports["getBuffer"] = Function::New(env, GetBuffer); exports["getByteOffset"] = Function::New(env, GetByteOffset); exports["getByteLength"] = Function::New(env, GetByteLength); diff --git a/test/dataview/dataview.js b/test/dataview/dataview.js index 4e3936457..5916f6b90 100644 --- a/test/dataview/dataview.js +++ b/test/dataview/dataview.js @@ -1,38 +1,74 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +module.exports = require('../common').runTest(test); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +let runSharedArrayBufferTests = true; -function test(binding) { - function testDataViewCreation(factory, arrayBuffer, offset, length) { +function test (binding) { + function testDataViewCreation (factory, arrayBuffer, offset, length) { const view = factory(arrayBuffer, offset, length); - offset = offset ? offset : 0; - assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); - assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + offset = offset || 0; + if (arrayBuffer instanceof ArrayBuffer) { + assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); + assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + } else { + assert.ok(dataview.getBuffer(view) instanceof SharedArrayBuffer); + assert.strictEqual(dataview.getBuffer(view), arrayBuffer); + } assert.strictEqual(dataview.getByteOffset(view), offset); assert.strictEqual(dataview.getByteLength(view), - length ? length : arrayBuffer.byteLength - offset); + length || arrayBuffer.byteLength - offset); } - function testInvalidRange(factory, arrayBuffer, offset, length) { + function testInvalidRange (factory, arrayBuffer, offset, length) { assert.throws(() => { factory(arrayBuffer, offset, length); }, RangeError); } - const dataview = binding.dataview; - const arrayBuffer = new ArrayBuffer(10); + const { hasSharedArrayBuffer, dataview } = binding; - testDataViewCreation(dataview.createDataView1, arrayBuffer); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 2); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 10); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0); + { + const arrayBuffer = new ArrayBuffer(10); - testInvalidRange(dataview.createDataView2, arrayBuffer, 11); - testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0); - testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5); + testDataViewCreation(dataview.createDataView, arrayBuffer); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 2); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 10); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 2, 4); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 10, 0); + + testInvalidRange(dataview.createDataViewWithByteOffset, arrayBuffer, 11); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 11, 0); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 6, 5); + } + + if (hasSharedArrayBuffer && runSharedArrayBufferTests) { + const sab = new SharedArrayBuffer(10); + + try { + testDataViewCreation(dataview.createDataViewOnSharedArrayBuffer, sab); + } catch (ex) { + // The `napi_create_dataview` API does not have a valid `#define` + // preprocessor guard for SharedArrayBuffer support, so it is + // possible that the API is present but creating a DataView on + // SharedArrayBuffer is not supported in the current version of Node.js. + // In that case, we should skip the test instead of throwing. + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating DataViews on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } + + throw ex; + } + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 2); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 10); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 2, 4); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 10, 0); + + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 11); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 11, 0); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 6, 5); + } } diff --git a/test/dataview/dataview_read_write.js b/test/dataview/dataview_read_write.js index 83d58cc11..708ef2855 100644 --- a/test/dataview/dataview_read_write.js +++ b/test/dataview/dataview_read_write.js @@ -1,57 +1,57 @@ +/* eslint-disable no-eval */ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function expected(type, value) { +function test (binding) { + function expected (type, value) { return eval(`(new ${type}Array([${value}]))[0]`); } - function nativeReadDataView(dataview, type, offset, value) { + function nativeReadDataView (dataview, type, offset, value) { return eval(`binding.dataview_read_write.get${type}(dataview, offset)`); } - function nativeWriteDataView(dataview, type, offset, value) { + function nativeWriteDataView (dataview, type, offset, value) { eval(`binding.dataview_read_write.set${type}(dataview, offset, value)`); } - function isLittleEndian() { + // eslint-disable-next-line no-unused-vars + function isLittleEndian () { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true /* littleEndian */); return new Int16Array(buffer)[0] === 256; } - function jsReadDataView(dataview, type, offset, value) { + function jsReadDataView (dataview, type, offset, value) { return eval(`dataview.get${type}(offset, isLittleEndian())`); } - function jsWriteDataView(dataview, type, offset, value) { + function jsWriteDataView (dataview, type, offset, value) { eval(`dataview.set${type}(offset, value, isLittleEndian())`); } - function testReadData(dataview, type, offset, value) { + function testReadData (dataview, type, offset, value) { jsWriteDataView(dataview, type, offset, 0); assert.strictEqual(jsReadDataView(dataview, type, offset), 0); jsWriteDataView(dataview, type, offset, value); assert.strictEqual( - nativeReadDataView(dataview, type, offset), expected(type, value)); + nativeReadDataView(dataview, type, offset), expected(type, value)); } - function testWriteData(dataview, type, offset, value) { + function testWriteData (dataview, type, offset, value) { jsWriteDataView(dataview, type, offset, 0); assert.strictEqual(jsReadDataView(dataview, type, offset), 0); nativeWriteDataView(dataview, type, offset, value); assert.strictEqual( - jsReadDataView(dataview, type, offset), expected(type, value)); + jsReadDataView(dataview, type, offset), expected(type, value)); } - function testInvalidOffset(dataview, type, offset, value) { + function testInvalidOffset (dataview, type, offset, value) { assert.throws(() => { nativeReadDataView(dataview, type, offset); }, RangeError); diff --git a/test/date.cc b/test/date.cc index c6e0dad21..a446bde22 100644 --- a/test/date.cc +++ b/test/date.cc @@ -11,6 +11,11 @@ Value CreateDate(const CallbackInfo& info) { return Date::New(info.Env(), input); } +Value CreateDateFromTimePoint(const CallbackInfo& info) { + auto input = std::chrono::system_clock::time_point{}; + return Date::New(info.Env(), input); +} + Value IsDate(const CallbackInfo& info) { Date input = info[0].As(); @@ -26,7 +31,8 @@ Value ValueOf(const CallbackInfo& info) { Value OperatorValue(const CallbackInfo& info) { Date input = info[0].As(); - return Boolean::New(info.Env(), input.ValueOf() == static_cast(input)); + return Boolean::New(info.Env(), + input.ValueOf() == static_cast(input)); } } // anonymous namespace @@ -34,6 +40,8 @@ Value OperatorValue(const CallbackInfo& info) { Object InitDate(Env env) { Object exports = Object::New(env); exports["CreateDate"] = Function::New(env, CreateDate); + exports["CreateDateFromTimePoint"] = + Function::New(env, CreateDateFromTimePoint); exports["IsDate"] = Function::New(env, IsDate); exports["ValueOf"] = Function::New(env, ValueOf); exports["OperatorValue"] = Function::New(env, OperatorValue); diff --git a/test/date.js b/test/date.js index 16e618e5b..588b741b1 100644 --- a/test/date.js +++ b/test/date.js @@ -1,19 +1,19 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { const { CreateDate, IsDate, ValueOf, OperatorValue, + CreateDateFromTimePoint } = binding.date; assert.deepStrictEqual(CreateDate(0), new Date(0)); + assert.deepStrictEqual(CreateDateFromTimePoint(), new Date(0)); assert.strictEqual(IsDate(new Date(0)), true); assert.strictEqual(ValueOf(new Date(42)), 42); assert.strictEqual(OperatorValue(new Date(42)), true); diff --git a/test/env_cleanup.cc b/test/env_cleanup.cc new file mode 100644 index 000000000..a0ef62b2c --- /dev/null +++ b/test/env_cleanup.cc @@ -0,0 +1,100 @@ +#include +#include "napi.h" + +using namespace Napi; + +#if (NAPI_VERSION > 2) +namespace { + +static void cleanup(void* arg) { + printf("static cleanup(%d)\n", *(int*)(arg)); +} +static void cleanupInt(int* arg) { + printf("static cleanup(%d)\n", *(arg)); +} + +static void cleanupVoid() { + printf("static cleanup()\n"); +} + +static int secret1 = 42; +static int secret2 = 43; + +class TestClass { + public: + Env::CleanupHook hook; + + void removeHook(Env env) { hook.Remove(env); } +}; + +Value AddHooks(const CallbackInfo& info) { + auto env = info.Env(); + + bool shouldRemove = info[0].As().Value(); + + // hook: void (*)(void *arg), hint: int + auto hook1 = env.AddCleanupHook(cleanup, &secret1); + // test using same hook+arg pair + auto hook1b = env.AddCleanupHook(cleanup, &secret1); + + // hook: void (*)(int *arg), hint: int + auto hook2 = env.AddCleanupHook(cleanupInt, &secret2); + + // hook: void (*)(int *arg), hint: void (default) + auto hook3 = env.AddCleanupHook(cleanupVoid); + // test using the same hook + auto hook3b = env.AddCleanupHook(cleanupVoid); + + // hook: lambda []void (int *arg)->void, hint: int + auto hook4 = env.AddCleanupHook( + [&](int* arg) { printf("lambda cleanup(%d)\n", *arg); }, &secret1); + + // hook: lambda []void (void *)->void, hint: void + auto hook5 = + env.AddCleanupHook([&](void*) { printf("lambda cleanup(void)\n"); }, + static_cast(nullptr)); + + // hook: lambda []void ()->void, hint: void (default) + auto hook6 = env.AddCleanupHook([&]() { printf("lambda cleanup()\n"); }); + + if (shouldRemove) { + hook1.Remove(env); + hook1b.Remove(env); + hook2.Remove(env); + hook3.Remove(env); + hook3b.Remove(env); + hook4.Remove(env); + hook5.Remove(env); + hook6.Remove(env); + } + + int added = 0; + + added += !hook1.IsEmpty(); + added += !hook1b.IsEmpty(); + added += !hook2.IsEmpty(); + added += !hook3.IsEmpty(); + added += !hook3b.IsEmpty(); + added += !hook4.IsEmpty(); + added += !hook5.IsEmpty(); + added += !hook6.IsEmpty(); + + // Test store a hook in a member class variable + auto myclass = TestClass(); + myclass.hook = env.AddCleanupHook(cleanup, &secret1); + myclass.removeHook(env); + + return Number::New(env, added); +} + +} // anonymous namespace + +Object InitEnvCleanup(Env env) { + Object exports = Object::New(env); + + exports["addHooks"] = Function::New(env, AddHooks); + + return exports; +} + +#endif diff --git a/test/env_cleanup.js b/test/env_cleanup.js new file mode 100644 index 000000000..ad515eb40 --- /dev/null +++ b/test/env_cleanup.js @@ -0,0 +1,55 @@ +'use strict'; + +const assert = require('assert'); + +if (process.argv[2] === 'runInChildProcess') { + const bindingPath = process.argv[3]; + const removeHooks = process.argv[4] === 'true'; + + const binding = require(bindingPath); + const actualAdded = binding.env_cleanup.addHooks(removeHooks); + const expectedAdded = removeHooks === true ? 0 : 8; + assert(actualAdded === expectedAdded, 'Incorrect number of hooks added'); +} else { + module.exports = require('./common').runTestWithBindingPath(test); +} + +function test (bindingPath) { + for (const removeHooks of [false, true]) { + const { status, output } = require('./napi_child').spawnSync( + process.execPath, + [ + __filename, + 'runInChildProcess', + bindingPath, + removeHooks + ], + { encoding: 'utf8' } + ); + + const stdout = output[1].trim(); + /** + * There is no need to sort the lines, as per Node-API documentation: + * > The hooks will be called in reverse order, i.e. the most recently + * > added one will be called first. + */ + const lines = stdout.split(/[\r\n]+/); + + assert(status === 0, `Process aborted with status ${status}`); + + if (removeHooks) { + assert.deepStrictEqual(lines, [''], 'Child process had console output when none expected'); + } else { + assert.deepStrictEqual(lines, [ + 'lambda cleanup()', + 'lambda cleanup(void)', + 'lambda cleanup(42)', + 'static cleanup()', + 'static cleanup()', + 'static cleanup(43)', + 'static cleanup(42)', + 'static cleanup(42)' + ], 'Child process console output mismisatch'); + } + } +} diff --git a/test/env_misc.cc b/test/env_misc.cc new file mode 100644 index 000000000..a453e5d0e --- /dev/null +++ b/test/env_misc.cc @@ -0,0 +1,25 @@ +#include "napi.h" +#include "test_helper.h" + +#if (NAPI_VERSION > 8) + +using namespace Napi; + +namespace { + +Value GetModuleFileName(const CallbackInfo& info) { + Env env = info.Env(); + return String::New(env, env.GetModuleFileName()); +} + +} // end anonymous namespace + +Object InitEnvMiscellaneous(Env env) { + Object exports = Object::New(env); + + exports["get_module_file_name"] = Function::New(env, GetModuleFileName); + + return exports; +} + +#endif diff --git a/test/env_misc.js b/test/env_misc.js new file mode 100644 index 000000000..19fc9881e --- /dev/null +++ b/test/env_misc.js @@ -0,0 +1,12 @@ +'use strict'; + +const assert = require('assert'); +const { pathToFileURL } = require('url'); + +module.exports = require('./common').runTest(test); + +function test (binding, { bindingPath } = {}) { + const path = binding.env_misc.get_module_file_name(); + const bindingFileUrl = pathToFileURL(bindingPath).toString(); + assert(bindingFileUrl === path); +} diff --git a/test/error.cc b/test/error.cc index 832cad525..6c716351b 100644 --- a/test/error.cc +++ b/test/error.cc @@ -1,9 +1,56 @@ +#include +#include +#include "assert.h" #include "napi.h" using namespace Napi; namespace { +std::promise promise_for_child_process_; +std::promise promise_for_worker_thread_; + +void ResetPromises(const CallbackInfo&) { + promise_for_child_process_ = std::promise(); + promise_for_worker_thread_ = std::promise(); +} + +void WaitForWorkerThread(const CallbackInfo&) { + std::future future = promise_for_worker_thread_.get_future(); + + std::future_status status = future.wait_for(std::chrono::seconds(5)); + + if (status != std::future_status::ready) { + Error::Fatal("WaitForWorkerThread", "status != std::future_status::ready"); + } +} + +void ReleaseAndWaitForChildProcess(const CallbackInfo& info, + const uint32_t index) { + if (info.Length() < index + 1) { + return; + } + + if (!info[index].As().Value()) { + return; + } + + promise_for_worker_thread_.set_value(); + + std::future future = promise_for_child_process_.get_future(); + + std::future_status status = future.wait_for(std::chrono::seconds(5)); + + if (status != std::future_status::ready) { + Error::Fatal("ReleaseAndWaitForChildProcess", + "status != std::future_status::ready"); + } +} + +void ReleaseWorkerThread(const CallbackInfo&) { + promise_for_child_process_.set_value(); +} + void DoNotCatch(const CallbackInfo& info) { Function thrower = info[0].As(); thrower({}); @@ -14,23 +61,119 @@ void ThrowApiError(const CallbackInfo& info) { Function(info.Env(), nullptr).Call(std::initializer_list{}); } +void LastExceptionErrorCode(const CallbackInfo& info) { + // Previously, `napi_extended_error_info.error_code` got reset to `napi_ok` in + // subsequent Node-API function calls, so this would have previously thrown an + // `Error` object instead of a `TypeError` object. + Env env = info.Env(); + bool res; + napi_get_value_bool(env, Value::From(env, "asd"), &res); + NAPI_THROW_VOID(Error::New(env)); +} + +void TestErrorCopySemantics(const Napi::CallbackInfo& info) { + Napi::Error newError = Napi::Error::New(info.Env(), "errorCopyCtor"); + Napi::Error existingErr; + +#ifdef NAPI_CPP_EXCEPTIONS + std::string msg = "errorCopyCtor"; + assert(strcmp(newError.what(), msg.c_str()) == 0); +#endif + + Napi::Error errCopyCtor = newError; + assert(errCopyCtor.Message() == "errorCopyCtor"); + + existingErr = newError; + assert(existingErr.Message() == "errorCopyCtor"); +} + +void TestErrorMoveSemantics(const Napi::CallbackInfo& info) { + std::string errorMsg = "errorMoveCtor"; + Napi::Error newError = Napi::Error::New(info.Env(), errorMsg.c_str()); + Napi::Error errFromMove = std::move(newError); + assert(errFromMove.Message() == "errorMoveCtor"); + + newError = Napi::Error::New(info.Env(), "errorMoveAssign"); + Napi::Error existingErr = std::move(newError); + + assert(existingErr.Message() == "errorMoveAssign"); +} + #ifdef NAPI_CPP_EXCEPTIONS void ThrowJSError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw Error::New(info.Env(), message); } +void ThrowTypeErrorCtor(const CallbackInfo& info) { + Napi::Value js_type_error = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + + throw Napi::TypeError(info.Env(), js_type_error); +} + void ThrowTypeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw TypeError::New(info.Env(), message); } +void ThrowTypeErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); + throw TypeError::New(info.Env(), message.c_str()); +} + +void ThrowRangeErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + ReleaseAndWaitForChildProcess(info, 1); + throw RangeError::New(info.Env(), message.c_str()); +} + +void ThrowRangeErrorCtor(const CallbackInfo& info) { + Napi::Value js_range_err = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + throw Napi::RangeError(info.Env(), js_range_err); +} + +void ThrowEmptyRangeError(const CallbackInfo& info) { + ReleaseAndWaitForChildProcess(info, 1); + throw RangeError(); +} + void ThrowRangeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw RangeError::New(info.Env(), message); } +#if NAPI_VERSION > 8 +void ThrowSyntaxErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + ReleaseAndWaitForChildProcess(info, 1); + throw SyntaxError::New(info.Env(), message.c_str()); +} + +void ThrowSyntaxErrorCtor(const CallbackInfo& info) { + Napi::Value js_range_err = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + throw Napi::SyntaxError(info.Env(), js_range_err); +} + +void ThrowSyntaxError(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); + throw SyntaxError::New(info.Env(), message); +} +#endif // NAPI_VERSION > 8 + Value CatchError(const CallbackInfo& info) { Function thrower = info[0].As(); try { @@ -79,23 +222,81 @@ void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { } } -#else // NAPI_CPP_EXCEPTIONS +#else // NAPI_CPP_EXCEPTIONS void ThrowJSError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); Error::New(info.Env(), message).ThrowAsJavaScriptException(); } void ThrowTypeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); TypeError::New(info.Env(), message).ThrowAsJavaScriptException(); } +void ThrowTypeErrorCtor(const CallbackInfo& info) { + Napi::Value js_type_error = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + TypeError(info.Env(), js_type_error).ThrowAsJavaScriptException(); +} + +void ThrowTypeErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); + TypeError::New(info.Env(), message.c_str()).ThrowAsJavaScriptException(); +} + void ThrowRangeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); RangeError::New(info.Env(), message).ThrowAsJavaScriptException(); } +void ThrowRangeErrorCtor(const CallbackInfo& info) { + Napi::Value js_range_err = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + RangeError(info.Env(), js_range_err).ThrowAsJavaScriptException(); +} + +void ThrowRangeErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + ReleaseAndWaitForChildProcess(info, 1); + RangeError::New(info.Env(), message.c_str()).ThrowAsJavaScriptException(); +} + +// TODO: Figure out the correct api for this +void ThrowEmptyRangeError(const CallbackInfo& info) { + ReleaseAndWaitForChildProcess(info, 1); + RangeError().ThrowAsJavaScriptException(); +} + +#if NAPI_VERSION > 8 +void ThrowSyntaxError(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); + SyntaxError::New(info.Env(), message).ThrowAsJavaScriptException(); +} + +void ThrowSyntaxErrorCtor(const CallbackInfo& info) { + Napi::Value js_range_err = info[0]; + ReleaseAndWaitForChildProcess(info, 1); + SyntaxError(info.Env(), js_range_err).ThrowAsJavaScriptException(); +} + +void ThrowSyntaxErrorCStr(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + ReleaseAndWaitForChildProcess(info, 1); + SyntaxError::New(info.Env(), message.c_str()).ThrowAsJavaScriptException(); +} +#endif // NAPI_VERSION > 8 + Value CatchError(const CallbackInfo& info) { Function thrower = info[0].As(); thrower({}); @@ -152,7 +353,7 @@ void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { } } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NAPI_CPP_EXCEPTIONS void ThrowFatalError(const CallbackInfo& /*info*/) { Error::Fatal("Error::ThrowFatalError", "This is a fatal error"); @@ -165,7 +366,7 @@ void ThrowDefaultError(const CallbackInfo& info) { NAPI_FATAL_IF_FAILED(status, "ThrowDefaultError", "napi_get_undefined"); if (info[0].As().Value()) { - // Provoke N-API into setting an error, then use the `Napi::Error::New` + // Provoke Node-API into setting an error, then use the `Napi::Error::New` // factory with only the `env` parameter to throw an exception generated // from the last error. uint32_t dummy_uint32; @@ -187,27 +388,49 @@ void ThrowDefaultError(const CallbackInfo& info) { Error::Fatal("ThrowDefaultError", "napi_get_named_property"); } + ReleaseAndWaitForChildProcess(info, 1); + // The macro creates a `Napi::Error` using the factory that takes only the // env, however, it heeds the exception mechanism to be used. NAPI_THROW_IF_FAILED_VOID(env, status); } -} // end anonymous namespace +} // end anonymous namespace Object InitError(Env env) { Object exports = Object::New(env); exports["throwApiError"] = Function::New(env, ThrowApiError); + exports["testErrorCopySemantics"] = + Function::New(env, TestErrorCopySemantics); + exports["testErrorMoveSemantics"] = + Function::New(env, TestErrorMoveSemantics); + exports["lastExceptionErrorCode"] = + Function::New(env, LastExceptionErrorCode); exports["throwJSError"] = Function::New(env, ThrowJSError); exports["throwTypeError"] = Function::New(env, ThrowTypeError); + exports["throwTypeErrorCtor"] = Function::New(env, ThrowTypeErrorCtor); + exports["throwTypeErrorCStr"] = Function::New(env, ThrowTypeErrorCStr); exports["throwRangeError"] = Function::New(env, ThrowRangeError); + exports["throwRangeErrorCtor"] = Function::New(env, ThrowRangeErrorCtor); + exports["throwRangeErrorCStr"] = Function::New(env, ThrowRangeErrorCStr); + exports["throwEmptyRangeError"] = Function::New(env, ThrowEmptyRangeError); +#if NAPI_VERSION > 8 + exports["throwSyntaxError"] = Function::New(env, ThrowSyntaxError); + exports["throwSyntaxErrorCtor"] = Function::New(env, ThrowSyntaxErrorCtor); + exports["throwSyntaxErrorCStr"] = Function::New(env, ThrowSyntaxErrorCStr); +#endif // NAPI_VERSION > 8 exports["catchError"] = Function::New(env, CatchError); exports["catchErrorMessage"] = Function::New(env, CatchErrorMessage); exports["doNotCatch"] = Function::New(env, DoNotCatch); exports["catchAndRethrowError"] = Function::New(env, CatchAndRethrowError); - exports["throwErrorThatEscapesScope"] = Function::New(env, ThrowErrorThatEscapesScope); + exports["throwErrorThatEscapesScope"] = + Function::New(env, ThrowErrorThatEscapesScope); exports["catchAndRethrowErrorThatEscapesScope"] = - Function::New(env, CatchAndRethrowErrorThatEscapesScope); + Function::New(env, CatchAndRethrowErrorThatEscapesScope); exports["throwFatalError"] = Function::New(env, ThrowFatalError); exports["throwDefaultError"] = Function::New(env, ThrowDefaultError); + exports["resetPromises"] = Function::New(env, ResetPromises); + exports["waitForWorkerThread"] = Function::New(env, WaitForWorkerThread); + exports["releaseWorkerThread"] = Function::New(env, ReleaseWorkerThread); return exports; } diff --git a/test/error.js b/test/error.js index 031db081a..c2a3ad367 100644 --- a/test/error.js +++ b/test/error.js @@ -1,41 +1,73 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); if (process.argv[2] === 'fatal') { const binding = require(process.argv[3]); binding.error.throwFatalError(); - return; } -test(`./build/${buildType}/binding.node`); -test(`./build/${buildType}/binding_noexcept.node`); +module.exports = require('./common').runTestWithBindingPath(test); + +const napiVersion = Number(process.env.NAPI_VERSION ?? process.versions.napi); -function test(bindingPath) { +function test (bindingPath) { const binding = require(bindingPath); + binding.error.testErrorCopySemantics(); + binding.error.testErrorMoveSemantics(); - assert.throws(() => binding.error.throwApiError('test'), function(err) { + assert.throws(() => binding.error.throwApiError('test'), function (err) { return err instanceof Error && err.message.includes('Invalid'); }); - assert.throws(() => binding.error.throwJSError('test'), function(err) { + assert.throws(() => binding.error.lastExceptionErrorCode(), function (err) { + return err instanceof TypeError && err.message === 'A boolean was expected'; + }); + + assert.throws(() => binding.error.throwJSError('test'), function (err) { return err instanceof Error && err.message === 'test'; }); - assert.throws(() => binding.error.throwTypeError('test'), function(err) { + assert.throws(() => binding.error.throwTypeErrorCStr('test'), function (err) { return err instanceof TypeError && err.message === 'test'; }); - assert.throws(() => binding.error.throwRangeError('test'), function(err) { + assert.throws(() => binding.error.throwRangeErrorCStr('test'), function (err) { return err instanceof RangeError && err.message === 'test'; }); + assert.throws(() => binding.error.throwRangeError('test'), function (err) { + return err instanceof RangeError && err.message === 'test'; + }); + + assert.throws(() => binding.error.throwTypeErrorCtor(new TypeError('jsTypeError')), function (err) { + return err instanceof TypeError && err.message === 'jsTypeError'; + }); + + assert.throws(() => binding.error.throwRangeErrorCtor(new RangeError('rangeTypeError')), function (err) { + return err instanceof RangeError && err.message === 'rangeTypeError'; + }); + + if (napiVersion > 8) { + assert.throws(() => binding.error.throwSyntaxErrorCStr('test'), function (err) { + return err instanceof SyntaxError && err.message === 'test'; + }); + + assert.throws(() => binding.error.throwSyntaxError('test'), function (err) { + return err instanceof SyntaxError && err.message === 'test'; + }); + + assert.throws(() => binding.error.throwSyntaxErrorCtor(new SyntaxError('syntaxTypeError')), function (err) { + return err instanceof SyntaxError && err.message === 'syntaxTypeError'; + }); + } + assert.throws( () => binding.error.doNotCatch( () => { throw new TypeError('test'); }), - function(err) { + function (err) { return err instanceof TypeError && err.message === 'test' && !err.caught; }); @@ -44,7 +76,7 @@ function test(bindingPath) { () => { throw new TypeError('test'); }), - function(err) { + function (err) { return err instanceof TypeError && err.message === 'test' && err.caught; }); @@ -57,19 +89,19 @@ function test(bindingPath) { () => { throw new TypeError('test'); }); assert.strictEqual(msg, 'test'); - assert.throws(() => binding.error.throwErrorThatEscapesScope('test'), function(err) { + assert.throws(() => binding.error.throwErrorThatEscapesScope('test'), function (err) { return err instanceof Error && err.message === 'test'; }); - assert.throws(() => binding.error.catchAndRethrowErrorThatEscapesScope('test'), function(err) { + assert.throws(() => binding.error.catchAndRethrowErrorThatEscapesScope('test'), function (err) { return err instanceof Error && err.message === 'test' && err.caught; }); const p = require('./napi_child').spawnSync( - process.execPath, [ __filename, 'fatal', bindingPath ]); + process.execPath, [__filename, 'fatal', bindingPath]); assert.ifError(p.error); assert.ok(p.stderr.toString().includes( - 'FATAL ERROR: Error::ThrowFatalError This is a fatal error')); + 'FATAL ERROR: Error::ThrowFatalError This is a fatal error')); assert.throws(() => binding.error.throwDefaultError(false), /Cannot convert undefined or null to object/); diff --git a/test/error_handling_for_primitives.cc b/test/error_handling_for_primitives.cc new file mode 100644 index 000000000..173293264 --- /dev/null +++ b/test/error_handling_for_primitives.cc @@ -0,0 +1,13 @@ +#include + +namespace { +void Test(const Napi::CallbackInfo& info) { + info[0].As().Call({}); +} + +} // namespace +Napi::Object InitErrorHandlingPrim(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + exports.Set("errorHandlingPrim", Napi::Function::New(env)); + return exports; +} diff --git a/test/error_handling_for_primitives.js b/test/error_handling_for_primitives.js new file mode 100644 index 000000000..105c2b605 --- /dev/null +++ b/test/error_handling_for_primitives.js @@ -0,0 +1,29 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest((binding) => { + test(binding.errorHandlingPrim); +}); + +function canThrow (binding, errorMessage, errorType) { + try { + binding.errorHandlingPrim(() => { + throw errorMessage; + }); + } catch (e) { + // eslint-disable-next-line valid-typeof + assert(typeof e === errorType); + assert(e === errorMessage); + } +} + +function test (binding) { + canThrow(binding, '404 server not found!', 'string'); + canThrow(binding, 42, 'number'); + canThrow(binding, Symbol.for('newSym'), 'symbol'); + canThrow(binding, false, 'boolean'); + canThrow(binding, BigInt(123), 'bigint'); + canThrow(binding, () => { console.log('Logger shutdown incorrectly'); }, 'function'); + canThrow(binding, { status: 403, errorMsg: 'Not authenticated' }, 'object'); +} diff --git a/test/error_terminating_environment.js b/test/error_terminating_environment.js new file mode 100644 index 000000000..df9e8b935 --- /dev/null +++ b/test/error_terminating_environment.js @@ -0,0 +1,99 @@ +'use strict'; + +const assert = require('assert'); +const { whichBuildType } = require('./common'); + +// These tests ensure that Error types can be used in a terminating +// environment without triggering any fatal errors. + +if (process.argv[2] === 'runInChildProcess') { + const bindingPath = process.argv[3]; + const indexForTestCase = Number(process.argv[4]); + + const binding = require(bindingPath); + + // Use C++ promises to ensure the worker thread is terminated right + // before running the testable code in the binding. + + binding.error.resetPromises(); + + const { Worker } = require('worker_threads'); + + const worker = new Worker( + __filename, + { + argv: [ + 'runInWorkerThread', + bindingPath, + indexForTestCase + ] + } + ); + + binding.error.waitForWorkerThread(); + + worker.terminate(); + + binding.error.releaseWorkerThread(); +} else { + if (process.argv[2] === 'runInWorkerThread') { + const bindingPath = process.argv[3]; + const indexForTestCase = Number(process.argv[4]); + + const binding = require(bindingPath); + + switch (indexForTestCase) { + case 0: + binding.error.throwJSError('test', true); + break; + case 1: + binding.error.throwTypeError('test', true); + break; + case 2: + binding.error.throwRangeError('test', true); + break; + case 3: + binding.error.throwDefaultError(false, true); + break; + case 4: + binding.error.throwDefaultError(true, true); + break; + default: assert.fail('Invalid index'); + } + + assert.fail('This should not be reachable'); + } + + wrapTest(); + + async function wrapTest () { + const buildType = await whichBuildType(); + test(`./build/${buildType}/binding.node`, true); + test(`./build/${buildType}/binding_noexcept.node`, true); + test(`./build/${buildType}/binding_swallowexcept.node`, false); + test(`./build/${buildType}/binding_swallowexcept_noexcept.node`, false); + test(`./build/${buildType}/binding_custom_namespace.node`, true); + } + + function test (bindingPath, processShouldAbort) { + const numberOfTestCases = 5; + + for (let i = 0; i < numberOfTestCases; ++i) { + const childProcess = require('./napi_child').spawnSync( + process.execPath, + [ + __filename, + 'runInChildProcess', + bindingPath, + i + ] + ); + + if (processShouldAbort) { + assert(childProcess.status !== 0, `Test case ${bindingPath} ${i} failed: Process exited with status code 0.`); + } else { + assert(childProcess.status === 0, `Test case ${bindingPath} ${i} failed: Process status ${childProcess.status} is non-zero`); + } + } + } +} diff --git a/test/except_all.cc b/test/except_all.cc new file mode 100644 index 000000000..e2c230b21 --- /dev/null +++ b/test/except_all.cc @@ -0,0 +1,22 @@ +#include +#include "napi.h" + +using namespace Napi; + +void ThrowStdException(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + throw std::runtime_error(message); +} + +void ThrowPrimitiveException(const CallbackInfo&) { + throw 0; +} + +Object Init(Env env, Object exports) { + exports.Set("throwStdException", Napi::Function::New(env, ThrowStdException)); + exports.Set("throwPrimitiveException", + Napi::Function::New(env, ThrowPrimitiveException)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/test/except_all.js b/test/except_all.js new file mode 100644 index 000000000..d650ece6f --- /dev/null +++ b/test/except_all.js @@ -0,0 +1,14 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTestWithBuildType(test); + +function test (buildType) { + const binding = require(`./build/${buildType}/binding_except_all.node`); + + const message = 'error message'; + assert.throws(binding.throwStdException.bind(undefined, message), { message }); + + assert.throws(binding.throwPrimitiveException.bind(undefined), { message: 'A native exception was thrown' }); +} diff --git a/test/exports.js b/test/exports.js new file mode 100644 index 000000000..1aa39281c --- /dev/null +++ b/test/exports.js @@ -0,0 +1,19 @@ +'use strict'; + +const { strictEqual } = require('assert'); +const { valid } = require('semver'); + +const nodeAddonApi = require('../'); + +module.exports = function test () { + strictEqual(nodeAddonApi.include.startsWith('"'), true); + strictEqual(nodeAddonApi.include.endsWith('"'), true); + strictEqual(nodeAddonApi.include.includes('node-addon-api'), true); + strictEqual(nodeAddonApi.include_dir, ''); + strictEqual(nodeAddonApi.gyp, 'node_api.gyp:nothing'); + strictEqual(nodeAddonApi.targets, 'node_addon_api.gyp'); + strictEqual(valid(nodeAddonApi.version), true); + strictEqual(nodeAddonApi.version, require('../package.json').version); + strictEqual(nodeAddonApi.isNodeApiBuiltin, true); + strictEqual(nodeAddonApi.needsFlag, false); +}; diff --git a/test/external.cc b/test/external.cc index 9c22dcbe8..255b17f19 100644 --- a/test/external.cc +++ b/test/external.cc @@ -14,66 +14,70 @@ Value CreateExternal(const CallbackInfo& info) { Value CreateExternalWithFinalize(const CallbackInfo& info) { finalizeCount = 0; - return External::New(info.Env(), new int(1), - [](Env /*env*/, int* data) { - delete data; - finalizeCount++; - }); + return External::New(info.Env(), new int(1), [](Env /*env*/, int* data) { + delete data; + finalizeCount++; + }); } Value CreateExternalWithFinalizeHint(const CallbackInfo& info) { finalizeCount = 0; char* hint = nullptr; - return External::New(info.Env(), new int(1), - [](Env /*env*/, int* data, char* /*hint*/) { - delete data; - finalizeCount++; - }, - hint); + return External::New( + info.Env(), + new int(1), + [](Env /*env*/, int* data, char* /*hint*/) { + delete data; + finalizeCount++; + }, + hint); } void CheckExternal(const CallbackInfo& info) { - Value arg = info[0]; - if (arg.Type() != napi_external) { - Error::New(info.Env(), "An external argument was expected.").ThrowAsJavaScriptException(); - return; - } + Value arg = info[0]; + if (arg.Type() != napi_external) { + Error::New(info.Env(), "An external argument was expected.") + .ThrowAsJavaScriptException(); + return; + } - External external = arg.As>(); - int* externalData = external.Data(); - if (externalData == nullptr || *externalData != 1) { - Error::New(info.Env(), "An external value of 1 was expected.").ThrowAsJavaScriptException(); - return; - } + External external = arg.As>(); + int* externalData = external.Data(); + if (externalData == nullptr || *externalData != 1) { + Error::New(info.Env(), "An external value of 1 was expected.") + .ThrowAsJavaScriptException(); + return; + } } Value GetFinalizeCount(const CallbackInfo& info) { - return Number::New(info.Env(), finalizeCount); + return Number::New(info.Env(), finalizeCount); } Value CreateExternalWithFinalizeException(const CallbackInfo& info) { - return External::New(info.Env(), new int(1), - [](Env env, int* data) { - Error error = Error::New(env, "Finalizer exception"); - delete data; + return External::New(info.Env(), new int(1), [](Env env, int* data) { + Error error = Error::New(env, "Finalizer exception"); + delete data; #ifdef NAPI_CPP_EXCEPTIONS - throw error; + throw error; #else error.ThrowAsJavaScriptException(); #endif - }); + }); } -} // end anonymous namespace +} // end anonymous namespace Object InitExternal(Env env) { Object exports = Object::New(env); exports["createExternal"] = Function::New(env, CreateExternal); - exports["createExternalWithFinalize"] = Function::New(env, CreateExternalWithFinalize); + exports["createExternalWithFinalize"] = + Function::New(env, CreateExternalWithFinalize); exports["createExternalWithFinalizeException"] = Function::New(env, CreateExternalWithFinalizeException); - exports["createExternalWithFinalizeHint"] = Function::New(env, CreateExternalWithFinalizeHint); + exports["createExternalWithFinalizeHint"] = + Function::New(env, CreateExternalWithFinalizeHint); exports["checkExternal"] = Function::New(env, CheckExternal); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); diff --git a/test/external.js b/test/external.js index 0443e3f55..e395ad1d9 100644 --- a/test/external.js +++ b/test/external.js @@ -1,5 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const { spawnSync } = require('child_process'); const testUtil = require('./testUtil'); @@ -27,64 +27,59 @@ if (process.argv.length === 3) { // exception is thrown from a native `SetImmediate()` we cannot catch it // anywhere except in the process' `uncaughtException` handler. let maxGCTries = 10; - (function gcInterval() { + (function gcInterval () { global.gc(); if (!interval) { interval = setInterval(gcInterval, 100); } else if (--maxGCTries === 0) { throw new Error('Timed out waiting for the gc to throw'); - process.exit(1); } })(); +} else { + module.exports = require('./common').runTestWithBindingPath(test); - return; -} - -module.exports = test(require.resolve(`./build/${buildType}/binding.node`)) - .then(() => - test(require.resolve(`./build/${buildType}/binding_noexcept.node`))); - -function test(bindingPath) { - const binding = require(bindingPath); + function test (bindingPath) { + const binding = require(bindingPath); - const child = spawnSync(process.execPath, [ - '--expose-gc', __filename, bindingPath - ], { stdio: 'inherit' }); - assert.strictEqual(child.status, 0); - assert.strictEqual(child.signal, null); + const child = spawnSync(process.execPath, [ + '--expose-gc', __filename, bindingPath + ], { stdio: 'inherit' }); + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); - return testUtil.runGCTests([ - 'External without finalizer', - () => { - const test = binding.external.createExternal(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, + return testUtil.runGCTests([ + 'External without finalizer', + () => { + const test = binding.external.createExternal(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, - 'External with finalizer', - () => { - const test = binding.external.createExternalWithFinalize(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(1, binding.external.getFinalizeCount()); - }, + 'External with finalizer', + () => { + const test = binding.external.createExternalWithFinalize(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(1, binding.external.getFinalizeCount()); + }, - 'External with finalizer hint', - () => { - const test = binding.external.createExternalWithFinalizeHint(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(1, binding.external.getFinalizeCount()); - }, - ]); + 'External with finalizer hint', + () => { + const test = binding.external.createExternalWithFinalizeHint(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(1, binding.external.getFinalizeCount()); + } + ]); + } } diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc new file mode 100644 index 000000000..0767ced70 --- /dev/null +++ b/test/finalizer_order.cc @@ -0,0 +1,152 @@ +#include + +namespace { +class Test : public Napi::ObjectWrap { + public: + Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + basicFinalizerCalled = false; + finalizerCalled = false; + + if (info.Length() > 0) { + finalizeCb_ = Napi::Persistent(info[0].As()); + } + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + exports.Set("Test", + DefineClass(env, + "Test", + { + StaticAccessor("isBasicFinalizerCalled", + &IsBasicFinalizerCalled, + nullptr, + napi_default), + StaticAccessor("isFinalizerCalled", + &IsFinalizerCalled, + nullptr, + napi_default), + })); + } + + void Finalize(Napi::BasicEnv /*env*/) { basicFinalizerCalled = true; } + + void Finalize(Napi::Env /*env*/) { + finalizerCalled = true; + if (!finalizeCb_.IsEmpty()) { + finalizeCb_.Call({}); + } + } + + static Napi::Value IsBasicFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), basicFinalizerCalled); + } + + static Napi::Value IsFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), finalizerCalled); + } + + private: + Napi::FunctionReference finalizeCb_; + + static bool basicFinalizerCalled; + static bool finalizerCalled; +}; + +bool Test::basicFinalizerCalled = false; +bool Test::finalizerCalled = false; + +bool externalBasicFinalizerCalled = false; +bool externalFinalizerCalled = false; + +Napi::Value CreateExternalBasicFinalizer(const Napi::CallbackInfo& info) { + externalBasicFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::BasicEnv /*env*/, int* data) { + externalBasicFinalizerCalled = true; + delete data; + }); +} + +Napi::Value CreateExternalFinalizer(const Napi::CallbackInfo& info) { + externalFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::Env /*env*/, int* data) { + externalFinalizerCalled = true; + delete data; + }); +} + +Napi::Value isExternalBasicFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalBasicFinalizerCalled); +} + +Napi::Value IsExternalFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalFinalizerCalled); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +Napi::Value PostFinalizer(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer([callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/) { callback.Call({}); }); + + return env.Undefined(); +} + +Napi::Value PostFinalizerWithData(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer( + [callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/, Napi::Reference* data) { + callback.Call({data->Value()}); + delete data; + }, + new Napi::Reference(Napi::Persistent(info[1]))); + + return env.Undefined(); +} + +Napi::Value PostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer( + [callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/, + Napi::Reference* data, + Napi::Reference* hint) { + callback.Call({data->Value(), hint->Value()}); + delete data; + delete hint; + }, + new Napi::Reference(Napi::Persistent(info[1])), + new Napi::Reference(Napi::Persistent(info[2]))); + + return env.Undefined(); +} +#endif + +} // namespace + +Napi::Object InitFinalizerOrder(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + Test::Initialize(env, exports); + exports["createExternalBasicFinalizer"] = + Napi::Function::New(env, CreateExternalBasicFinalizer); + exports["createExternalFinalizer"] = + Napi::Function::New(env, CreateExternalFinalizer); + exports["isExternalBasicFinalizerCalled"] = + Napi::Function::New(env, isExternalBasicFinalizerCalled); + exports["isExternalFinalizerCalled"] = + Napi::Function::New(env, IsExternalFinalizerCalled); + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + exports["PostFinalizer"] = Napi::Function::New(env, PostFinalizer); + exports["PostFinalizerWithData"] = + Napi::Function::New(env, PostFinalizerWithData); + exports["PostFinalizerWithDataAndHint"] = + Napi::Function::New(env, PostFinalizerWithDataAndHint); +#endif + return exports; +} diff --git a/test/finalizer_order.js b/test/finalizer_order.js new file mode 100644 index 000000000..4b267a0d0 --- /dev/null +++ b/test/finalizer_order.js @@ -0,0 +1,98 @@ +'use strict'; + +/* eslint-disable no-unused-vars */ + +const assert = require('assert'); +const common = require('./common'); +const testUtil = require('./testUtil'); + +module.exports = require('./common').runTest(test); + +function test (binding) { + const { isExperimental } = binding; + + let isCallbackCalled = false; + + const tests = [ + 'Finalizer Order - ObjectWrap', + () => { + let test = new binding.finalizer_order.Test(() => { isCallbackCalled = true; }); + test = null; + + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, false, 'Expected basic finalizer to not be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [after ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, true, 'Expected (extended) finalizer to be called [after ticking]'); + assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]'); + }, + + 'Finalizer Order - External with Basic Finalizer', + () => { + let ext = binding.finalizer_order.createExternalBasicFinalizer(); + ext = null; + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), false, 'Expected External basic finalizer to not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [after ticking]'); + }, + + 'Finalizer Order - External with Finalizer', + () => { + let ext = binding.finalizer_order.createExternalFinalizer(); + ext = null; + global.gc(); + assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), false, 'Expected External extended finalizer to not be called [before ticking]'); + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), true, 'Expected External extended finalizer to be called [after ticking]'); + } + ]; + + if (binding.isExperimental) { + tests.push(...[ + 'PostFinalizer', + () => { + binding.finalizer_order.PostFinalizer(common.mustCall()); + }, + + 'PostFinalizerWithData', + () => { + const data = {}; + const callback = (callbackData) => { + assert.strictEqual(callbackData, data); + }; + binding.finalizer_order.PostFinalizerWithData(common.mustCall(callback), data); + }, + + 'PostFinalizerWithDataAndHint', + () => { + const data = {}; + const hint = {}; + const callback = (callbackData, callbackHint) => { + assert.strictEqual(callbackData, data); + assert.strictEqual(callbackHint, hint); + }; + binding.finalizer_order.PostFinalizerWithDataAndHint(common.mustCall(callback), data, hint); + } + ]); + } + + return testUtil.runGCTests(tests); +} diff --git a/test/function.cc b/test/function.cc index b0ae92c7d..5c1c6e163 100644 --- a/test/function.cc +++ b/test/function.cc @@ -1,4 +1,6 @@ +#include #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,6 +8,13 @@ namespace { int testData = 1; +Boolean EmptyConstructor(const CallbackInfo& info) { + auto env = info.Env(); + bool isEmpty = info[0].As(); + Function function = isEmpty ? Function() : Function(env, Object::New(env)); + return Boolean::New(env, function.IsEmpty()); +} + void VoidCallback(const CallbackInfo& info) { auto env = info.Env(); Object obj = info[0].As(); @@ -45,105 +54,285 @@ Value ValueCallbackWithData(const CallbackInfo& info) { } Value CallWithArgs(const CallbackInfo& info) { - Function func = info[0].As(); - return func({ info[1], info[2], info[3] }); + Function func = info[0].As(); + return MaybeUnwrap( + func.Call(std::initializer_list{info[1], info[2], info[3]})); } Value CallWithVector(const CallbackInfo& info) { - Function func = info[0].As(); - std::vector args; - args.reserve(3); - args.push_back(info[1]); - args.push_back(info[2]); - args.push_back(info[3]); - return func.Call(args); + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return MaybeUnwrap(func.Call(args)); +} + +Value CallWithVectorUsingCppWrapper(const CallbackInfo& info) { + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return MaybeUnwrap(func.Call(args)); +} + +Value CallWithCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return MaybeUnwrap(func.Call(args.size(), args.data())); +} + +Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + Value receiver = info[1]; + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + return MaybeUnwrap(func.Call(receiver, args.size(), args.data())); } Value CallWithReceiverAndArgs(const CallbackInfo& info) { - Function func = info[0].As(); - Value receiver = info[1]; - return func.Call(receiver, std::initializer_list{ info[2], info[3], info[4] }); + Function func = info[0].As(); + Value receiver = info[1]; + return MaybeUnwrap(func.Call( + receiver, std::initializer_list{info[2], info[3], info[4]})); } Value CallWithReceiverAndVector(const CallbackInfo& info) { - Function func = info[0].As(); - Value receiver = info[1]; - std::vector args; - args.reserve(3); - args.push_back(info[2]); - args.push_back(info[3]); - args.push_back(info[4]); - return func.Call(receiver, args); + Function func = info[0].As(); + Value receiver = info[1]; + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + return MaybeUnwrap(func.Call(receiver, args)); +} + +Value CallWithReceiverAndVectorUsingCppWrapper(const CallbackInfo& info) { + Function func = info[0].As(); + Value receiver = info[1]; + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + return MaybeUnwrap(func.Call(receiver, args)); } Value CallWithInvalidReceiver(const CallbackInfo& info) { - Function func = info[0].As(); - return func.Call(Value(), std::initializer_list{}); + Function func = info[0].As(); + return MaybeUnwrapOr(func.Call(Value(), std::initializer_list{}), + Value()); } Value CallConstructorWithArgs(const CallbackInfo& info) { - Function func = info[0].As(); - return func.New(std::initializer_list{ info[1], info[2], info[3] }); + Function func = info[0].As(); + return MaybeUnwrap( + func.New(std::initializer_list{info[1], info[2], info[3]})); } Value CallConstructorWithVector(const CallbackInfo& info) { - Function func = info[0].As(); - std::vector args; - args.reserve(3); - args.push_back(info[1]); - args.push_back(info[2]); - args.push_back(info[3]); - return func.New(args); + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return MaybeUnwrap(func.New(args)); +} + +Value CallConstructorWithCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return MaybeUnwrap(func.New(args.size(), args.data())); } void IsConstructCall(const CallbackInfo& info) { - Function callback = info[0].As(); - bool isConstructCall = info.IsConstructCall(); - callback({Napi::Boolean::New(info.Env(), isConstructCall)}); + Function callback = info[0].As(); + bool isConstructCall = info.IsConstructCall(); + callback({Napi::Boolean::New(info.Env(), isConstructCall)}); +} + +Value NewTargetCallback(const CallbackInfo& info) { + return info.NewTarget(); +} + +void MakeCallbackWithArgs(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + callback.MakeCallback( + resource, + std::initializer_list{info[2], info[3], info[4]}, + context); +} + +void MakeCallbackWithVector(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + callback.MakeCallback(resource, args, context); +} + +void MakeCallbackWithCStyleArray(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + callback.MakeCallback(resource, args.size(), args.data(), context); +} + +void MakeCallbackWithInvalidReceiver(const CallbackInfo& info) { + Function callback = info[0].As(); + callback.MakeCallback(Value(), std::initializer_list{}); +} + +Value CallWithFunctionOperator(const CallbackInfo& info) { + Function func = info[0].As(); + return MaybeUnwrap(func({info[1], info[2], info[3]})); } -} // end anonymous namespace +} // end anonymous namespace Object InitFunction(Env env) { Object result = Object::New(env); Object exports = Object::New(env); + exports["emptyConstructor"] = Function::New(env, EmptyConstructor); exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback"); - exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback")); + exports["valueCallback"] = + Function::New(env, ValueCallback, std::string("valueCallback")); exports["voidCallbackWithData"] = - Function::New(env, VoidCallbackWithData, nullptr, &testData); + Function::New(env, VoidCallbackWithData, nullptr, &testData); exports["valueCallbackWithData"] = - Function::New(env, ValueCallbackWithData, nullptr, &testData); + Function::New(env, ValueCallbackWithData, nullptr, &testData); + exports["newTargetCallback"] = + Function::New(env, NewTargetCallback, std::string("newTargetCallback")); exports["callWithArgs"] = Function::New(env, CallWithArgs); exports["callWithVector"] = Function::New(env, CallWithVector); - exports["callWithReceiverAndArgs"] = Function::New(env, CallWithReceiverAndArgs); - exports["callWithReceiverAndVector"] = Function::New(env, CallWithReceiverAndVector); - exports["callWithInvalidReceiver"] = Function::New(env, CallWithInvalidReceiver); - exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs); - exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector); + exports["callWithVectorUsingCppWrapper"] = + Function::New(env, CallWithVectorUsingCppWrapper); + exports["callWithCStyleArray"] = Function::New(env, CallWithCStyleArray); + exports["callWithReceiverAndCStyleArray"] = + Function::New(env, CallWithReceiverAndCStyleArray); + exports["callWithReceiverAndArgs"] = + Function::New(env, CallWithReceiverAndArgs); + exports["callWithReceiverAndVector"] = + Function::New(env, CallWithReceiverAndVector); + exports["callWithReceiverAndVectorUsingCppWrapper"] = + Function::New(env, CallWithReceiverAndVectorUsingCppWrapper); + exports["callWithInvalidReceiver"] = + Function::New(env, CallWithInvalidReceiver); + exports["callConstructorWithArgs"] = + Function::New(env, CallConstructorWithArgs); + exports["callConstructorWithVector"] = + Function::New(env, CallConstructorWithVector); + exports["callConstructorWithCStyleArray"] = + Function::New(env, CallConstructorWithCStyleArray); exports["isConstructCall"] = Function::New(env, IsConstructCall); + exports["makeCallbackWithArgs"] = Function::New(env, MakeCallbackWithArgs); + exports["makeCallbackWithVector"] = + Function::New(env, MakeCallbackWithVector); + exports["makeCallbackWithCStyleArray"] = + Function::New(env, MakeCallbackWithCStyleArray); + exports["makeCallbackWithInvalidReceiver"] = + Function::New(env, MakeCallbackWithInvalidReceiver); + exports["callWithFunctionOperator"] = + Function::New(env, CallWithFunctionOperator); result["plain"] = exports; exports = Object::New(env); + exports["emptyConstructor"] = Function::New(env, EmptyConstructor); exports["voidCallback"] = Function::New(env, "voidCallback"); exports["valueCallback"] = Function::New(env, std::string("valueCallback")); + exports["newTargetCallback"] = + Function::New(env, std::string("newTargetCallback")); exports["voidCallbackWithData"] = Function::New(env, nullptr, &testData); exports["valueCallbackWithData"] = Function::New(env, nullptr, &testData); exports["callWithArgs"] = Function::New(env); exports["callWithVector"] = Function::New(env); + exports["callWithVectorUsingCppWrapper"] = + Function::New(env); + exports["callWithCStyleArray"] = Function::New(env); + exports["callWithReceiverAndCStyleArray"] = + Function::New(env); exports["callWithReceiverAndArgs"] = Function::New(env); exports["callWithReceiverAndVector"] = Function::New(env); + exports["callWithReceiverAndVectorUsingCppWrapper"] = + Function::New(env); exports["callWithInvalidReceiver"] = Function::New(env); exports["callConstructorWithArgs"] = Function::New(env); exports["callConstructorWithVector"] = Function::New(env); + exports["callConstructorWithCStyleArray"] = + Function::New(env); exports["isConstructCall"] = Function::New(env); + exports["makeCallbackWithArgs"] = Function::New(env); + exports["makeCallbackWithVector"] = + Function::New(env); + exports["makeCallbackWithCStyleArray"] = + Function::New(env); + exports["makeCallbackWithInvalidReceiver"] = + Function::New(env); + exports["callWithFunctionOperator"] = + Function::New(env); result["templated"] = exports; + + exports = Object::New(env); + exports["lambdaWithNoCapture"] = + Function::New(env, [](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, true); + }); + exports["lambdaWithCapture"] = + Function::New(env, [data = 42](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, data == 42); + }); + exports["lambdaWithMoveOnlyCapture"] = Function::New( + env, [data = std::make_unique(42)](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, *data == 42); + }); + result["lambda"] = exports; + return result; } diff --git a/test/function.js b/test/function.js index 8ab742c27..c5514db36 100644 --- a/test/function.js +++ b/test/function.js @@ -1,77 +1,137 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`).function.plain); -test(require(`./build/${buildType}/binding_noexcept.node`).function.plain); -test(require(`./build/${buildType}/binding.node`).function.templated); -test(require(`./build/${buildType}/binding_noexcept.node`).function.templated); +module.exports = require('./common').runTest(binding => { + test(binding.function.plain); + test(binding.function.templated); + testLambda(binding.function.lambda); +}); + +function test (binding) { + assert.strictEqual(binding.emptyConstructor(true), true); + assert.strictEqual(binding.emptyConstructor(false), false); -function test(binding) { let obj = {}; assert.deepStrictEqual(binding.voidCallback(obj), undefined); - assert.deepStrictEqual(obj, { "foo": "bar" }); + assert.deepStrictEqual(obj, { foo: 'bar' }); + + assert.deepStrictEqual(binding.valueCallback(), { foo: 'bar' }); - assert.deepStrictEqual(binding.valueCallback(), { "foo": "bar" }); + /* eslint-disable-next-line new-cap */ + assert.strictEqual(new binding.newTargetCallback(), binding.newTargetCallback); + assert.strictEqual(binding.newTargetCallback(), undefined); let args = null; let ret = null; let receiver = null; - function testFunction() { + function testFunction () { receiver = this; args = [].slice.call(arguments); return ret; } - function testConstructor() { + function testConstructor () { args = [].slice.call(arguments); } + function makeCallbackTestFunction (receiver, expectedOne, expectedTwo, expectedThree) { + return function callback (one, two, three) { + assert.strictEqual(this, receiver); + assert.strictEqual(one, expectedOne); + assert.strictEqual(two, expectedTwo); + assert.strictEqual(three, expectedThree); + }; + } + ret = 4; - assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4); + assert.strictEqual(binding.callWithArgs(testFunction, 1, 2, 3), 4); assert.strictEqual(receiver, undefined); - assert.deepStrictEqual(args, [ 1, 2, 3 ]); + assert.deepStrictEqual(args, [1, 2, 3]); ret = 5; - assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5); + assert.strictEqual(binding.callWithVector(testFunction, 2, 3, 4), 5); assert.strictEqual(receiver, undefined); - assert.deepStrictEqual(args, [ 2, 3, 4 ]); + assert.deepStrictEqual(args, [2, 3, 4]); + + ret = 5; + assert.strictEqual(binding.callWithVectorUsingCppWrapper(testFunction, 2, 3, 4), 5); + assert.strictEqual(receiver, undefined); + assert.deepStrictEqual(args, [2, 3, 4]); ret = 6; - assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.strictEqual(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.deepStrictEqual(receiver, obj); + assert.deepStrictEqual(args, [3, 4, 5]); + + ret = 7; + assert.strictEqual(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); assert.deepStrictEqual(receiver, obj); - assert.deepStrictEqual(args, [ 3, 4, 5 ]); + assert.deepStrictEqual(args, [4, 5, 6]); ret = 7; - assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); + assert.strictEqual(binding.callWithReceiverAndVectorUsingCppWrapper(testFunction, obj, 4, 5, 6), 7); assert.deepStrictEqual(receiver, obj); - assert.deepStrictEqual(args, [ 4, 5, 6 ]); + assert.deepStrictEqual(args, [4, 5, 6]); + + ret = 8; + assert.strictEqual(binding.callWithCStyleArray(testFunction, 5, 6, 7), ret); + assert.deepStrictEqual(receiver, undefined); + assert.deepStrictEqual(args, [5, 6, 7]); + + ret = 9; + assert.strictEqual(binding.callWithReceiverAndCStyleArray(testFunction, obj, 6, 7, 8), ret); + assert.deepStrictEqual(receiver, obj); + assert.deepStrictEqual(args, [6, 7, 8]); + + ret = 10; + assert.strictEqual(binding.callWithFunctionOperator(testFunction, 7, 8, 9), ret); + assert.strictEqual(receiver, undefined); + assert.deepStrictEqual(args, [7, 8, 9]); assert.throws(() => { - binding.callWithInvalidReceiver(); + binding.callWithInvalidReceiver(() => {}); }, /Invalid (pointer passed as )?argument/); obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7); assert(obj instanceof testConstructor); - assert.deepStrictEqual(args, [ 5, 6, 7 ]); + assert.deepStrictEqual(args, [5, 6, 7]); obj = binding.callConstructorWithVector(testConstructor, 6, 7, 8); assert(obj instanceof testConstructor); - assert.deepStrictEqual(args, [ 6, 7, 8 ]); + assert.deepStrictEqual(args, [6, 7, 8]); + + obj = binding.callConstructorWithCStyleArray(testConstructor, 7, 8, 9); + assert(obj instanceof testConstructor); + assert.deepStrictEqual(args, [7, 8, 9]); obj = {}; assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined); - assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); + assert.deepStrictEqual(obj, { foo: 'bar', data: 1 }); - assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 }); + assert.deepStrictEqual(binding.valueCallbackWithData(), { foo: 'bar', data: 1 }); - assert.equal(binding.voidCallback.name, 'voidCallback'); - assert.equal(binding.valueCallback.name, 'valueCallback'); + assert.strictEqual(binding.voidCallback.name, 'voidCallback'); + assert.strictEqual(binding.valueCallback.name, 'valueCallback'); - let testConstructCall = undefined; + let testConstructCall; binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(!testConstructCall); + /* eslint-disable no-new, new-cap */ new binding.isConstructCall((result) => { testConstructCall = result; }); + /* eslint-enable no-new, new-cap */ assert.ok(testConstructCall); - // TODO: Function::MakeCallback tests + obj = {}; + binding.makeCallbackWithArgs(makeCallbackTestFunction(obj, '1', '2', '3'), obj, '1', '2', '3'); + binding.makeCallbackWithVector(makeCallbackTestFunction(obj, 4, 5, 6), obj, 4, 5, 6); + binding.makeCallbackWithCStyleArray(makeCallbackTestFunction(obj, 7, 8, 9), obj, 7, 8, 9); + assert.throws(() => { + binding.makeCallbackWithInvalidReceiver(() => {}); + }); +} + +function testLambda (binding) { + assert.ok(binding.lambdaWithNoCapture()); + assert.ok(binding.lambdaWithCapture()); + assert.ok(binding.lambdaWithMoveOnlyCapture()); } diff --git a/test/function_reference.cc b/test/function_reference.cc new file mode 100644 index 000000000..b50eb46fd --- /dev/null +++ b/test/function_reference.cc @@ -0,0 +1,205 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +class FuncRefObject : public Napi::ObjectWrap { + public: + FuncRefObject(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + int argLen = info.Length(); + if (argLen <= 0 || !info[0].IsNumber()) { + Napi::TypeError::New(env, "First param should be a number") + .ThrowAsJavaScriptException(); + return; + } + Napi::Number value = info[0].As(); + this->_value = value.Int32Value(); + } + + Napi::Value GetValue(const Napi::CallbackInfo& info) { + int value = this->_value; + return Napi::Number::New(info.Env(), value); + } + + private: + int _value; +}; + +namespace { + +Value ConstructRefFromExisitingRef(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + FunctionReference movedRef; + ref.Reset(info[0].As()); + movedRef = std::move(ref); + + return scope.Escape(MaybeUnwrap(movedRef({}))); +} + +Value CallWithVectorArgs(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + std::vector newVec; + FunctionReference ref; + ref.Reset(info[0].As()); + + for (int i = 1; i < (int)info.Length(); i++) { + newVec.push_back(info[i]); + } + return scope.Escape(MaybeUnwrap(ref.Call(newVec))); +} + +Value CallWithInitList(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return scope.Escape(MaybeUnwrap(ref.Call({info[1], info[2], info[3]}))); +} + +Value CallWithRecvInitList(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return scope.Escape( + MaybeUnwrap(ref.Call(info[1], {info[2], info[3], info[4]}))); +} + +Value CallWithRecvVector(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + std::vector newVec; + ref.Reset(info[0].As()); + + for (int i = 2; i < (int)info.Length(); i++) { + newVec.push_back(info[i]); + } + return scope.Escape(MaybeUnwrap(ref.Call(info[1], newVec))); +} + +Value CallWithRecvArgc(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + size_t argLength = info.Length() > 2 ? info.Length() - 2 : 0; + std::unique_ptr args{argLength > 0 ? new napi_value[argLength] + : nullptr}; + for (size_t i = 0; i < argLength; ++i) { + args[i] = info[i + 2]; + } + + return scope.Escape(MaybeUnwrap(ref.Call(info[1], argLength, args.get()))); +} + +Value MakeAsyncCallbackWithInitList(const Napi::CallbackInfo& info) { + Napi::FunctionReference ref; + ref.Reset(info[0].As()); + + Napi::AsyncContext context(info.Env(), "func_ref_resources", {}); + + return MaybeUnwrap( + ref.MakeCallback(Napi::Object::New(info.Env()), {}, context)); +} + +Value MakeAsyncCallbackWithVector(const Napi::CallbackInfo& info) { + Napi::FunctionReference ref; + ref.Reset(info[0].As()); + std::vector newVec; + Napi::AsyncContext context(info.Env(), "func_ref_resources", {}); + + for (int i = 1; i < (int)info.Length(); i++) { + newVec.push_back(info[i]); + } + + return MaybeUnwrap( + ref.MakeCallback(Napi::Object::New(info.Env()), newVec, context)); +} + +Value MakeAsyncCallbackWithArgv(const Napi::CallbackInfo& info) { + Napi::FunctionReference ref; + ref.Reset(info[0].As()); + + size_t argLength = info.Length() > 1 ? info.Length() - 1 : 0; + std::unique_ptr args{argLength > 0 ? new napi_value[argLength] + : nullptr}; + for (size_t i = 0; i < argLength; ++i) { + args[i] = info[i + 1]; + } + + Napi::AsyncContext context(info.Env(), "func_ref_resources", {}); + return MaybeUnwrap(ref.MakeCallback(Napi::Object::New(info.Env()), + argLength, + argLength > 0 ? args.get() : nullptr, + context)); +} + +Value CreateFunctionReferenceUsingNew(const Napi::CallbackInfo& info) { + Napi::Function func = ObjectWrap::DefineClass( + info.Env(), + "MyObject", + {ObjectWrap::InstanceMethod("getValue", + &FuncRefObject::GetValue)}); + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + + return MaybeUnwrapOr(constructor->New({info[0].As()}), Object()); +} + +Value CreateFunctionReferenceUsingNewVec(const Napi::CallbackInfo& info) { + Napi::Function func = ObjectWrap::DefineClass( + info.Env(), + "MyObject", + {ObjectWrap::InstanceMethod("getValue", + &FuncRefObject::GetValue)}); + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + std::vector newVec; + newVec.push_back(info[0]); + + return MaybeUnwrapOr(constructor->New(newVec), Object()); +} + +Value Call(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return scope.Escape(MaybeUnwrapOr(ref.Call({}), Value())); +} + +Value Construct(const CallbackInfo& info) { + EscapableHandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return scope.Escape(MaybeUnwrapOr(ref.New({}), Object())); +} +} // namespace + +Object InitFunctionReference(Env env) { + Object exports = Object::New(env); + exports["CreateFuncRefWithNew"] = + Function::New(env, CreateFunctionReferenceUsingNew); + exports["CreateFuncRefWithNewVec"] = + Function::New(env, CreateFunctionReferenceUsingNewVec); + exports["CallWithRecvArgc"] = Function::New(env, CallWithRecvArgc); + exports["CallWithRecvVector"] = Function::New(env, CallWithRecvVector); + exports["CallWithRecvInitList"] = Function::New(env, CallWithRecvInitList); + exports["CallWithInitList"] = Function::New(env, CallWithInitList); + exports["CallWithVec"] = Function::New(env, CallWithVectorArgs); + exports["ConstructWithMove"] = + Function::New(env, ConstructRefFromExisitingRef); + exports["AsyncCallWithInitList"] = + Function::New(env, MakeAsyncCallbackWithInitList); + exports["AsyncCallWithVector"] = + Function::New(env, MakeAsyncCallbackWithVector); + exports["AsyncCallWithArgv"] = Function::New(env, MakeAsyncCallbackWithArgv); + exports["call"] = Function::New(env, Call); + exports["construct"] = Function::New(env, Construct); + + return exports; +} diff --git a/test/function_reference.js b/test/function_reference.js new file mode 100644 index 000000000..e69fc8e5b --- /dev/null +++ b/test/function_reference.js @@ -0,0 +1,157 @@ +'use strict'; + +const assert = require('assert'); +const asyncHook = require('async_hooks'); + +module.exports = require('./common').runTest(async (binding) => { + await test(binding.functionreference); +}); + +function installAsyncHook () { + let id; + let destroyed; + let hook; + const events = []; + return new Promise((resolve, reject) => { + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + + hook = asyncHook + .createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === 'func_ref_resources') { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + destroyed = true; + } + } + }) + .enable(); + }); +} + +function canConstructRefFromExistingRef (binding) { + const testFunc = () => 240; + assert(binding.ConstructWithMove(testFunc) === 240); +} + +function canCallFunctionWithDifferentOverloads (binding) { + let outsideRef = {}; + const testFunc = (a, b) => a * a - b * b; + const testFuncB = (a, b, c) => a + b - c * c; + const testFuncC = (a, b, c) => { + outsideRef.a = a; + outsideRef.b = b; + outsideRef.c = c; + }; + const testFuncD = (a, b, c, d) => { + outsideRef.result = a + b * c - d; + return outsideRef.result; + }; + + assert(binding.CallWithVec(testFunc, 5, 4) === testFunc(5, 4)); + assert(binding.CallWithInitList(testFuncB, 2, 4, 5) === testFuncB(2, 4, 5)); + + binding.CallWithRecvVector(testFuncC, outsideRef, 1, 2, 4); + assert(outsideRef.a === 1 && outsideRef.b === 2 && outsideRef.c === 4); + + outsideRef = {}; + binding.CallWithRecvInitList(testFuncC, outsideRef, 1, 2, 4); + assert(outsideRef.a === 1 && outsideRef.b === 2 && outsideRef.c === 4); + + outsideRef = {}; + binding.CallWithRecvArgc(testFuncD, outsideRef, 2, 4, 5, 6); + assert(outsideRef.result === testFuncD(2, 4, 5, 6)); +} + +async function canCallAsyncFunctionWithDifferentOverloads (binding) { + const testFunc = () => 2100; + const testFuncB = (a, b, c, d) => a + b + c + d; + let hook = installAsyncHook(); + binding.AsyncCallWithInitList(testFunc); + let triggerAsyncId = asyncHook.executionAsyncId(); + let res = await hook; + assert.deepStrictEqual(res, [ + { + eventName: 'init', + type: 'func_ref_resources', + triggerAsyncId, + resource: {} + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + + hook = installAsyncHook(); + triggerAsyncId = asyncHook.executionAsyncId(); + assert( + binding.AsyncCallWithVector(testFuncB, 2, 4, 5, 6) === testFuncB(2, 4, 5, 6) + ); + res = await hook; + assert.deepStrictEqual(res, [ + { + eventName: 'init', + type: 'func_ref_resources', + triggerAsyncId, + resource: {} + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + + hook = installAsyncHook(); + triggerAsyncId = asyncHook.executionAsyncId(); + assert( + binding.AsyncCallWithArgv(testFuncB, 2, 4, 5, 6) === testFuncB(2, 4, 5, 6) + ); +} +async function test (binding) { + const e = new Error('foobar'); + const functionMayThrow = () => { + throw e; + }; + const classMayThrow = class { + constructor () { + throw e; + } + }; + + const newRef = binding.CreateFuncRefWithNew(120); + assert(newRef.getValue() === 120); + + const newRefWithVecArg = binding.CreateFuncRefWithNewVec(80); + assert(newRefWithVecArg.getValue() === 80); + + assert.throws(() => { + binding.call(functionMayThrow); + }, /foobar/); + assert.throws(() => { + binding.construct(classMayThrow); + }, /foobar/); + + canConstructRefFromExistingRef(binding); + canCallFunctionWithDifferentOverloads(binding); + await canCallAsyncFunctionWithDifferentOverloads(binding); +} diff --git a/test/globalObject/global_object.cc b/test/globalObject/global_object.cc new file mode 100644 index 000000000..00ef2ba8f --- /dev/null +++ b/test/globalObject/global_object.cc @@ -0,0 +1,61 @@ +#include "napi.h" + +using namespace Napi; + +// Wrappers for testing Object::Get() for global Objects +Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value GetPropertyWithInt32AsKey(const CallbackInfo& info); +Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info); +void CreateMockTestObject(const CallbackInfo& info); + +// Wrapper for testing Object::Set() for global Objects +void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info); +void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +void SetPropertyWithInt32AsKey(const CallbackInfo& info); +void SetPropertyWithNapiValueAsKey(const CallbackInfo& info); + +Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info); + +Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value DeletePropertyWithInt32AsKey(const CallbackInfo& info); +Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info); + +Object InitGlobalObject(Env env) { + Object exports = Object::New(env); + exports["getPropertyWithInt32"] = + Function::New(env, GetPropertyWithInt32AsKey); + exports["getPropertyWithNapiValue"] = + Function::New(env, GetPropertyWithNapiValueAsKey); + exports["getPropertyWithCppString"] = + Function::New(env, GetPropertyWithCppStyleStringAsKey); + exports["getPropertyWithCString"] = + Function::New(env, GetPropertyWithCStyleStringAsKey); + exports["createMockTestObject"] = Function::New(env, CreateMockTestObject); + exports["setPropertyWithCStyleString"] = + Function::New(env, SetPropertyWithCStyleStringAsKey); + exports["setPropertyWithCppStyleString"] = + Function::New(env, SetPropertyWithCppStyleStringAsKey); + exports["setPropertyWithNapiValue"] = + Function::New(env, SetPropertyWithNapiValueAsKey); + exports["setPropertyWithInt32"] = + Function::New(env, SetPropertyWithInt32AsKey); + exports["hasPropertyWithCStyleString"] = + Function::New(env, HasPropertyWithCStyleStringAsKey); + exports["hasPropertyWithCppStyleString"] = + Function::New(env, HasPropertyWithCppStyleStringAsKey); + exports["hasPropertyWithNapiValue"] = + Function::New(env, HasPropertyWithNapiValueAsKey); + exports["deletePropertyWithCStyleString"] = + Function::New(env, DeletePropertyWithCStyleStringAsKey); + exports["deletePropertyWithCppStyleString"] = + Function::New(env, DeletePropertyWithCppStyleStringAsKey); + exports["deletePropertyWithInt32"] = + Function::New(env, DeletePropertyWithInt32AsKey); + exports["deletePropertyWithNapiValue"] = + Function::New(env, DeletePropertyWithNapiValueAsKey); + return exports; +} diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc new file mode 100644 index 000000000..70738c2f4 --- /dev/null +++ b/test/globalObject/global_object_delete_property.cc @@ -0,0 +1,31 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + return Boolean::New( + info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value().c_str()))); +} + +Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Utf8Value()))); +} + +Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].UnsafeAs(); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Uint32Value()))); +} + +Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].UnsafeAs(); + return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key))); +} diff --git a/test/globalObject/global_object_delete_property.js b/test/globalObject/global_object_delete_property.js new file mode 100644 index 000000000..c8fa68e55 --- /dev/null +++ b/test/globalObject/global_object_delete_property.js @@ -0,0 +1,58 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function assertNotGlobalObjectHasNoProperty (key, keyType) { + switch (keyType) { + case KEY_TYPE.NAPI: + assert.notStrictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); + break; + + case KEY_TYPE.C_STR: + assert.notStrictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); + break; + + case KEY_TYPE.CPP_STR: + assert.notStrictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); + break; + + case KEY_TYPE.INT_32: + assert.notStrictEqual(binding.globalObject.hasPropertyWithInt32(key), true); + break; + } + } + + function assertErrMessageIsThrown (propertyCheckExistenceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistenceFunction(undefined); + }, errMsg); + } + + binding.globalObject.createMockTestObject(); + + binding.globalObject.deletePropertyWithCStyleString('c_str_key'); + binding.globalObject.deletePropertyWithCppStyleString('cpp_string_key'); + binding.globalObject.deletePropertyWithCppStyleString('circular'); + binding.globalObject.deletePropertyWithInt32(15); + binding.globalObject.deletePropertyWithNapiValue('2'); + + assertNotGlobalObjectHasNoProperty('c_str_key', KEY_TYPE.C_STR); + assertNotGlobalObjectHasNoProperty('cpp_string_key', KEY_TYPE.CPP_STR); + assertNotGlobalObjectHasNoProperty('circular', KEY_TYPE.CPP_STR); + assertNotGlobalObjectHasNoProperty(15, true); + assertNotGlobalObjectHasNoProperty('2', KEY_TYPE.NAPI); + + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc new file mode 100644 index 000000000..81f727d91 --- /dev/null +++ b/test/globalObject/global_object_get_property.cc @@ -0,0 +1,40 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].UnsafeAs(); + return MaybeUnwrap(globalObject.Get(key)); +} + +Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].UnsafeAs(); + return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); +} + +Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String cStrkey = info[0].UnsafeAs(); + return MaybeUnwrapOr(globalObject.Get(cStrkey.Utf8Value().c_str()), Value()); +} + +Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String cppStrKey = info[0].UnsafeAs(); + return MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); +} + +void CreateMockTestObject(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number napi_key = Number::New(info.Env(), 2); + const char* CStringKey = "c_str_key"; + + globalObject.Set(napi_key, "napi_attribute"); + globalObject[CStringKey] = "c_string_attribute"; + globalObject[std::string("cpp_string_key")] = "cpp_string_attribute"; + globalObject[std::string("circular")] = globalObject; + globalObject[(uint32_t)15] = 15; +} diff --git a/test/globalObject/global_object_get_property.js b/test/globalObject/global_object_get_property.js new file mode 100644 index 000000000..ec72a8447 --- /dev/null +++ b/test/globalObject/global_object_get_property.js @@ -0,0 +1,56 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + binding.globalObject.createMockTestObject(); + function assertGlobalObjectPropertyIs (key, attribute, keyType) { + let napiObjectAttr; + switch (keyType) { + case KEY_TYPE.NAPI: + napiObjectAttr = binding.globalObject.getPropertyWithNapiValue(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.C_STR: + napiObjectAttr = binding.globalObject.getPropertyWithCString(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.CPP_STR: + napiObjectAttr = binding.globalObject.getPropertyWithCppString(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.INT_32: + napiObjectAttr = binding.globalObject.getPropertyWithInt32(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + } + } + + function assertErrMessageIsThrown (propertyFetchFunction, errMsg) { + assert.throws(() => { + propertyFetchFunction(undefined); + }, errMsg); + } + + assertGlobalObjectPropertyIs('2', global['2'], KEY_TYPE.NAPI); + assertGlobalObjectPropertyIs('c_str_key', global.c_str_key, KEY_TYPE.C_STR); + assertGlobalObjectPropertyIs('cpp_string_key', global.cpp_string_key, KEY_TYPE.CPP_STR); + assertGlobalObjectPropertyIs('circular', global.circular, KEY_TYPE.CPP_STR); + assertGlobalObjectPropertyIs(15, global['15'], KEY_TYPE.INT_32); + + assertErrMessageIsThrown(binding.globalObject.getPropertyWithCString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.getPropertyWithCppString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.getPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_has_own_property.cc b/test/globalObject/global_object_has_own_property.cc new file mode 100644 index 000000000..388788d97 --- /dev/null +++ b/test/globalObject/global_object_has_own_property.cc @@ -0,0 +1,28 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()), + false)); +} + +Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()), false)); +} + +Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].UnsafeAs(); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.HasOwnProperty(key))); +} diff --git a/test/globalObject/global_object_has_own_property.js b/test/globalObject/global_object_has_own_property.js new file mode 100644 index 000000000..652c6b3fd --- /dev/null +++ b/test/globalObject/global_object_has_own_property.js @@ -0,0 +1,46 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function assertGlobalObjectHasProperty (key, keyType) { + switch (keyType) { + case KEY_TYPE.NAPI: + assert.strictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); + break; + + case KEY_TYPE.C_STR: + assert.strictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); + break; + + case KEY_TYPE.CPP_STR: + assert.strictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); + break; + } + } + + function assertErrMessageIsThrown (propertyCheckExistenceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistenceFunction(undefined); + }, errMsg); + } + + binding.globalObject.createMockTestObject(); + assertGlobalObjectHasProperty('c_str_key', KEY_TYPE.C_STR); + assertGlobalObjectHasProperty('cpp_string_key', KEY_TYPE.CPP_STR); + assertGlobalObjectHasProperty('circular', KEY_TYPE.CPP_STR); + assertGlobalObjectHasProperty('2', KEY_TYPE.NAPI); + + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_set_property.cc b/test/globalObject/global_object_set_property.cc new file mode 100644 index 000000000..06da6315a --- /dev/null +++ b/test/globalObject/global_object_set_property.cc @@ -0,0 +1,31 @@ +#include "napi.h" + +using namespace Napi; + +void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + Value value = info[1]; + globalObject.Set(key.Utf8Value().c_str(), value); +} + +void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].UnsafeAs(); + Value value = info[1]; + globalObject.Set(key.Utf8Value(), value); +} + +void SetPropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].UnsafeAs(); + Value value = info[1]; + globalObject.Set(key.Uint32Value(), value); +} + +void SetPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].UnsafeAs(); + Value value = info[1]; + globalObject.Set(key, value); +} diff --git a/test/globalObject/global_object_set_property.js b/test/globalObject/global_object_set_property.js new file mode 100644 index 000000000..ff4811c66 --- /dev/null +++ b/test/globalObject/global_object_set_property.js @@ -0,0 +1,56 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function setGlobalObjectKeyValue (key, value, keyType) { + switch (keyType) { + case KEY_TYPE.CPP_STR: + binding.globalObject.setPropertyWithCppStyleString(key, value); + break; + + case KEY_TYPE.C_STR: + binding.globalObject.setPropertyWithCStyleString(key, value); + break; + + case KEY_TYPE.INT_32: + binding.globalObject.setPropertyWithInt32(key, value); + break; + + case KEY_TYPE.NAPI: + binding.globalObject.setPropertyWithNapiValue(key, value); + break; + } + } + + function assertErrMessageIsThrown (nativeObjectSetFunction, errMsg) { + assert.throws(() => { + nativeObjectSetFunction(undefined, 1); + }, errMsg); + } + + setGlobalObjectKeyValue('cKey', 'cValue', KEY_TYPE.CPP_STR); + setGlobalObjectKeyValue(1, 10, KEY_TYPE.INT_32); + setGlobalObjectKeyValue('napi_key', 'napi_value', KEY_TYPE.NAPI); + setGlobalObjectKeyValue('cppKey', 'cppValue', KEY_TYPE.CPP_STR); + setGlobalObjectKeyValue('circular', global, KEY_TYPE.NAPI); + + assert.deepStrictEqual(global.circular, global); + assert.deepStrictEqual(global.cppKey, 'cppValue'); + assert.deepStrictEqual(global.napi_key, 'napi_value'); + assert.deepStrictEqual(global[1], 10); + assert.deepStrictEqual(global.cKey, 'cValue'); + + assertErrMessageIsThrown(binding.globalObject.setPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.setPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.setPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/handlescope.cc b/test/handlescope.cc index 9c958850d..c68c1c3fa 100644 --- a/test/handlescope.cc +++ b/test/handlescope.cc @@ -1,7 +1,7 @@ -#include "napi.h" -#include "string.h" #include #include +#include "napi.h" +#include "string.h" using namespace Napi; @@ -13,6 +13,16 @@ Value createScope(const CallbackInfo& info) { return String::New(info.Env(), "scope"); } +Value createScopeFromExisting(const CallbackInfo& info) { + { + napi_handle_scope scope; + napi_open_handle_scope(info.Env(), &scope); + HandleScope scope_existing(info.Env(), scope); + String::New(scope_existing.Env(), "inner-existing-scope"); + } + return String::New(info.Env(), "existing_scope"); +} + Value escapeFromScope(const CallbackInfo& info) { Value result; { @@ -22,6 +32,18 @@ Value escapeFromScope(const CallbackInfo& info) { return result; } +Value escapeFromExistingScope(const CallbackInfo& info) { + Value result; + { + napi_escapable_handle_scope scope; + napi_open_escapable_handle_scope(info.Env(), &scope); + EscapableHandleScope scope_existing(info.Env(), scope); + result = scope_existing.Escape( + String::New(scope_existing.Env(), "inner-existing-scope")); + } + return result; +} + #define LOOP_MAX 1000000 Value stressEscapeFromScope(const CallbackInfo& info) { Value result; @@ -31,7 +53,7 @@ Value stressEscapeFromScope(const CallbackInfo& info) { snprintf(buffer, 128, "%d", i); std::string name = std::string("inner-scope") + std::string(buffer); Value newValue = String::New(info.Env(), name.c_str()); - if (i == (LOOP_MAX -1)) { + if (i == (LOOP_MAX - 1)) { result = scope.Escape(newValue); } } @@ -52,7 +74,11 @@ Object InitHandleScope(Env env) { Object exports = Object::New(env); exports["createScope"] = Function::New(env, createScope); + exports["createScopeFromExisting"] = + Function::New(env, createScopeFromExisting); exports["escapeFromScope"] = Function::New(env, escapeFromScope); + exports["escapeFromExistingScope"] = + Function::New(env, escapeFromExistingScope); exports["stressEscapeFromScope"] = Function::New(env, stressEscapeFromScope); exports["doubleEscapeFromScope"] = Function::New(env, doubleEscapeFromScope); diff --git a/test/handlescope.js b/test/handlescope.js index 71cb89783..ad45f9503 100644 --- a/test/handlescope.js +++ b/test/handlescope.js @@ -1,15 +1,16 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { assert.strictEqual(binding.handlescope.createScope(), 'scope'); + assert.strictEqual(binding.handlescope.createScopeFromExisting(), 'existing_scope'); assert.strictEqual(binding.handlescope.escapeFromScope(), 'inner-scope'); + assert.strictEqual(binding.handlescope.escapeFromExistingScope(), 'inner-existing-scope'); assert.strictEqual(binding.handlescope.stressEscapeFromScope(), 'inner-scope999999'); assert.throws(() => binding.handlescope.doubleEscapeFromScope(), - Error, - ' napi_escape_handle already called on scope'); + Error, + ' napi_escape_handle already called on scope'); } diff --git a/test/index.js b/test/index.js index 9a174d367..f0c034584 100644 --- a/test/index.js +++ b/test/index.js @@ -1,74 +1,97 @@ 'use strict'; -process.config.target_defaults.default_configuration = - require('fs') - .readdirSync(require('path').join(__dirname, 'build')) - .filter((item) => (item === 'Debug' || item === 'Release'))[0]; - -// FIXME: We might need a way to load test modules automatically without -// explicit declaration as follows. -let testModules = [ - 'addon_build', - 'addon', - 'addon_data', - 'arraybuffer', - 'asynccontext', - 'asyncprogressqueueworker', - 'asyncprogressworker', - 'asyncworker', - 'asyncworker-nocallback', - 'asyncworker-persistent', - 'basic_types/array', - 'basic_types/boolean', - 'basic_types/number', - 'basic_types/value', - 'bigint', - 'date', - 'buffer', - 'callbackscope', - 'dataview/dataview', - 'dataview/dataview_read_write', - 'error', - 'external', - 'function', - 'handlescope', - 'memory_management', - 'name', - 'object/delete_property', - 'object/finalizer', - 'object/get_property', - 'object/has_own_property', - 'object/has_property', - 'object/object', - 'object/object_deprecated', - 'object/set_property', - 'promise', - 'run_script', - 'threadsafe_function/threadsafe_function_ctx', - 'threadsafe_function/threadsafe_function_existing_tsfn', - 'threadsafe_function/threadsafe_function_ptr', - 'threadsafe_function/threadsafe_function_sum', - 'threadsafe_function/threadsafe_function_unref', - 'threadsafe_function/threadsafe_function', - 'typed_threadsafe_function/typed_threadsafe_function_ctx', - 'typed_threadsafe_function/typed_threadsafe_function_existing_tsfn', - 'typed_threadsafe_function/typed_threadsafe_function_ptr', - 'typed_threadsafe_function/typed_threadsafe_function_sum', - 'typed_threadsafe_function/typed_threadsafe_function_unref', - 'typed_threadsafe_function/typed_threadsafe_function', - 'typedarray', - 'typedarray-bigint', - 'objectwrap', - 'objectwrap_constructor_exception', - 'objectwrap-removewrap', - 'objectwrap_multiple_inheritance', - 'objectwrap_worker_thread', - 'objectreference', - 'reference', - 'version_management' -]; - -let napiVersion = Number(process.versions.napi) +const majorNodeVersion = process.versions.node.split('.')[0]; + +if (typeof global.gc !== 'function') { + // Construct the correct (version-dependent) command-line args. + const args = ['--expose-gc']; + const majorV8Version = process.versions.v8.split('.')[0]; + if (majorV8Version < 9) { + args.push('--no-concurrent-array-buffer-freeing'); + } + if (majorNodeVersion >= 14) { + args.push('--no-concurrent-array-buffer-sweeping'); + } + args.push(__filename); + + const child = require('./napi_child').spawnSync(process.argv[0], args, { + stdio: 'inherit' + }); + + if (child.signal) { + console.error(`Tests aborted with ${child.signal}`); + process.exitCode = 1; + } else { + process.exitCode = child.status; + } + process.exit(process.exitCode); +} + +const testModules = []; + +const fs = require('fs'); +const path = require('path'); + +let filterCondition = process.env.npm_config_filter || ''; +let filterConditionFiles = []; + +if (filterCondition !== '') { + filterCondition = require('../unit-test/matchModules').matchWildCards(process.env.npm_config_filter); + filterConditionFiles = filterCondition.split(' ').length > 0 ? filterCondition.split(' ') : [filterCondition]; +} + +const filterConditionsProvided = filterConditionFiles.length > 0; + +function checkFilterCondition (fileName, parsedFilepath) { + let result = false; + + if (!filterConditionsProvided) return true; + if (filterConditionFiles.includes(parsedFilepath)) result = true; + if (filterConditionFiles.includes(fileName)) result = true; + return result; +} + +// TODO(RaisinTen): Update this when the test filenames +// are changed into test_*.js. +function loadTestModules (currentDirectory = __dirname, pre = '') { + fs.readdirSync(currentDirectory).forEach((file) => { + if (currentDirectory === __dirname && ( + file === 'binding.cc' || + file === 'binding.gyp' || + file === 'build' || + file === 'common' || + file === 'child_processes' || + file === 'napi_child.js' || + file === 'testUtil.js' || + file === 'thunking_manual.cc' || + file === 'thunking_manual.js' || + file === 'index.js' || + file[0] === '.')) { + return; + } + const absoluteFilepath = path.join(currentDirectory, file); + const parsedFilepath = path.parse(file); + const parsedPath = path.parse(currentDirectory); + + if (fs.statSync(absoluteFilepath).isDirectory()) { + if (fs.existsSync(absoluteFilepath + '/index.js')) { + if (checkFilterCondition(parsedFilepath.name, parsedPath.base)) { + testModules.push(pre + file); + } + } else { + loadTestModules(absoluteFilepath, pre + file + '/'); + } + } else { + if (parsedFilepath.ext === '.js' && checkFilterCondition(parsedFilepath.name, parsedPath.base)) { + testModules.push(pre + parsedFilepath.name); + } + } + }); +} + +loadTestModules(); + +let napiVersion = Number(process.versions.napi); if (process.env.NAPI_VERSION) { // we need this so that we don't try run tests that rely // on methods that are not available in the NAPI_VERSION @@ -77,14 +100,13 @@ if (process.env.NAPI_VERSION) { } console.log('napiVersion:' + napiVersion); -const majorNodeVersion = process.versions.node.split('.')[0] - if (napiVersion < 3) { + testModules.splice(testModules.indexOf('env_cleanup'), 1); testModules.splice(testModules.indexOf('callbackscope'), 1); testModules.splice(testModules.indexOf('version_management'), 1); } -if (napiVersion < 4) { +if (napiVersion < 4 && !filterConditionsProvided) { testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); @@ -95,55 +117,44 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } -if (napiVersion < 5) { +if (napiVersion < 5 && !filterConditionsProvided) { testModules.splice(testModules.indexOf('date'), 1); } -if (napiVersion < 6) { +if (napiVersion < 6 && !filterConditionsProvided) { testModules.splice(testModules.indexOf('addon'), 1); testModules.splice(testModules.indexOf('addon_data'), 1); testModules.splice(testModules.indexOf('bigint'), 1); testModules.splice(testModules.indexOf('typedarray-bigint'), 1); } -if (majorNodeVersion < 12) { +if (majorNodeVersion < 12 && !filterConditionsProvided) { testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); + testModules.splice(testModules.indexOf('error_terminating_environment'), 1); } -if (typeof global.gc === 'function') { - (async function() { - console.log(`Testing with N-API Version '${napiVersion}'.`); +if (napiVersion < 8 && !filterConditionsProvided) { + testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1); + testModules.splice(testModules.indexOf('type_taggable'), 1); +} - console.log('Starting test suite\n'); +if (napiVersion < 9 && !filterConditionsProvided) { + testModules.splice(testModules.indexOf('env_misc'), 1); +} + +(async function () { + console.log(`Testing with Node-API Version '${napiVersion}'.`); + + if (filterConditionsProvided) { console.log('Starting test suite\n', testModules); } else { console.log('Starting test suite\n'); } // Requiring each module runs tests in the module. for (const name of testModules) { console.log(`Running test '${name}'`); await require('./' + name); - }; - - console.log('\nAll tests passed!'); - })().catch((error) => { - console.log(error); - process.exit(1); - }); -} else { - // Construct the correct (version-dependent) command-line args. - let args = ['--expose-gc', '--no-concurrent-array-buffer-freeing']; - if (majorNodeVersion >= 14) { - args.push('--no-concurrent-array-buffer-sweeping'); } - args.push(__filename); - const child = require('./napi_child').spawnSync(process.argv[0], args, { - stdio: 'inherit', - }); - - if (child.signal) { - console.error(`Tests aborted with ${child.signal}`); - process.exitCode = 1; - } else { - process.exitCode = child.status; - } - process.exit(process.exitCode); -} + console.log('\nAll tests passed!'); +})().catch((error) => { + console.log(error); + process.exit(1); +}); diff --git a/test/maybe/check.cc b/test/maybe/check.cc new file mode 100644 index 000000000..74acf7e38 --- /dev/null +++ b/test/maybe/check.cc @@ -0,0 +1,69 @@ +#include "assert.h" +#include "napi.h" +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + +using namespace Napi; + +namespace { + +void VoidCallback(const CallbackInfo& info) { + Napi::Function fn = info[0].As(); + Maybe ret = fn.Call({}); + + assert(ret.IsNothing() == true); + assert(ret.IsJust() == false); + + Napi::Value placeHolder = Napi::Number::New(info.Env(), 12345); + Napi::Value unwrappedValue = ret.UnwrapOr(placeHolder); + + assert(unwrappedValue.As().Uint32Value() == 12345); + + assert(ret.UnwrapTo(&placeHolder) == false); + assert(placeHolder.As().Uint32Value() == 12345); + + ret.Check(); +} + +void TestMaybeOperatorOverload(const CallbackInfo& info) { + Napi::Function fn_a = info[0].As(); + Napi::Function fn_b = info[1].As(); + + assert(fn_a.Call({}) == fn_a.Call({})); + assert(fn_a.Call({}) != fn_b.Call({})); +} + +void NormalJsCallback(const CallbackInfo& info) { + Napi::Function fn = info[0].As(); + uint32_t magic_number = info[1].As().Uint32Value(); + + Maybe ret = fn.Call({}); + + assert(ret.IsNothing() == false); + assert(ret.IsJust() == true); + + Napi::Value unwrappedValue = ret.Unwrap(); + assert(unwrappedValue.IsNumber() == true); + + assert(unwrappedValue.As().Uint32Value() == magic_number); + + unwrappedValue = + ret.UnwrapOr(Napi::Number::New(info.Env(), magic_number - 1)); + assert(unwrappedValue.As().Uint32Value() == magic_number); + + Napi::Value placeHolder = Napi::Number::New(info.Env(), magic_number - 1); + assert(ret.UnwrapTo(&placeHolder) == true); + assert(placeHolder.As().Uint32Value() == magic_number); +} + +} // end anonymous namespace + +Object InitMaybeCheck(Env env) { + Object exports = Object::New(env); + exports.Set("voidCallback", Function::New(env, VoidCallback)); + exports.Set("normalJsCallback", Function::New(env, NormalJsCallback)); + exports.Set("testMaybeOverloadOp", + Function::New(env, TestMaybeOperatorOverload)); + return exports; +} + +#endif diff --git a/test/maybe/index.js b/test/maybe/index.js new file mode 100644 index 000000000..65f8643c2 --- /dev/null +++ b/test/maybe/index.js @@ -0,0 +1,50 @@ +'use strict'; + +const assert = require('assert'); +const { whichBuildType } = require('../common'); + +const napiChild = require('../napi_child'); + +module.exports = async function wrapTest () { + const buildType = await whichBuildType(); + test(require(`../build/${buildType}/binding_noexcept_maybe.node`).maybe_check); +}; + +function test (binding) { + if (process.argv.includes('child')) { + child(binding); + return; + } + const cp = napiChild.spawn(process.execPath, [__filename, 'child'], { + stdio: ['ignore', 'inherit', 'pipe'] + }); + cp.stderr.setEncoding('utf8'); + let stderr = ''; + cp.stderr.on('data', chunk => { + stderr += chunk; + }); + cp.on('exit', (code, signal) => { + if (process.platform === 'win32') { + assert.strictEqual(code, 128 + 6 /* SIGABRT */); + } else { + assert.strictEqual(signal, 'SIGABRT'); + } + assert.ok(stderr.match(/FATAL ERROR: Napi::Maybe::Check Maybe value is Nothing./)); + }); +} + +function child (binding) { + const MAGIC_NUMBER = 12459062; + binding.normalJsCallback(() => { + return MAGIC_NUMBER; + }, MAGIC_NUMBER); + + binding.testMaybeOverloadOp( + () => { return MAGIC_NUMBER; }, + () => { throw Error('Foobar'); } + ); + + binding.voidCallback(() => { + throw new Error('foobar'); + }); +} diff --git a/test/memory_management.cc b/test/memory_management.cc index a42357539..48bc389d4 100644 --- a/test/memory_management.cc +++ b/test/memory_management.cc @@ -3,15 +3,16 @@ using namespace Napi; Value externalAllocatedMemory(const CallbackInfo& info) { - int64_t kSize = 1024 * 1024; - int64_t baseline = MemoryManagement::AdjustExternalMemory(info.Env(), 0); - int64_t tmp = MemoryManagement::AdjustExternalMemory(info.Env(), kSize); - tmp = MemoryManagement::AdjustExternalMemory(info.Env(), -kSize); - return Boolean::New(info.Env(), tmp == baseline); + int64_t kSize = 1024 * 1024; + int64_t baseline = MemoryManagement::AdjustExternalMemory(info.Env(), 0); + int64_t tmp = MemoryManagement::AdjustExternalMemory(info.Env(), kSize); + tmp = MemoryManagement::AdjustExternalMemory(info.Env(), -kSize); + return Boolean::New(info.Env(), tmp == baseline); } Object InitMemoryManagement(Env env) { - Object exports = Object::New(env); - exports["externalAllocatedMemory"] = Function::New(env, externalAllocatedMemory); - return exports; + Object exports = Object::New(env); + exports["externalAllocatedMemory"] = + Function::New(env, externalAllocatedMemory); + return exports; } diff --git a/test/memory_management.js b/test/memory_management.js index f4911a2a6..af5dbb0f0 100644 --- a/test/memory_management.js +++ b/test/memory_management.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { - assert.strictEqual(binding.memory_management.externalAllocatedMemory(), true) +function test (binding) { + assert.strictEqual(binding.memory_management.externalAllocatedMemory(), true); } diff --git a/test/movable_callbacks.cc b/test/movable_callbacks.cc new file mode 100644 index 000000000..9959d4d38 --- /dev/null +++ b/test/movable_callbacks.cc @@ -0,0 +1,23 @@ +#include "napi.h" + +using namespace Napi; + +Value createExternal(const CallbackInfo& info) { + FunctionReference ref = Reference::New(info[0].As(), 1); + auto ret = External::New( + info.Env(), + nullptr, + [ref = std::move(ref)](Napi::Env /*env*/, char* /*data*/) { + ref.Call({}); + }); + + return ret; +} + +Object InitMovableCallbacks(Env env) { + Object exports = Object::New(env); + + exports["createExternal"] = Function::New(env, createExternal); + + return exports; +} diff --git a/test/movable_callbacks.js b/test/movable_callbacks.js new file mode 100644 index 000000000..c925b922d --- /dev/null +++ b/test/movable_callbacks.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('./common'); +const testUtil = require('./testUtil'); + +module.exports = require('./common').runTest(binding => test(binding.movable_callbacks)); + +async function test (binding) { + await testUtil.runGCTests([ + 'External', + () => { + const fn = common.mustCall(() => { + // noop + }, 1); + binding.createExternal(fn); + }, + () => { + // noop, wait for gc + } + ]); +} diff --git a/test/name.cc b/test/name.cc index 3a296ec58..d94a3937f 100644 --- a/test/name.cc +++ b/test/name.cc @@ -1,5 +1,7 @@ #include "napi.h" +#include + using namespace Napi; const char* testValueUtf8 = "123456789"; @@ -21,19 +23,21 @@ Value EchoString(const CallbackInfo& info) { Value CreateString(const CallbackInfo& info) { String encoding = info[0].As(); - Number length = info[1].As(); + Value length = info[1]; if (encoding.Utf8Value() == "utf8") { if (length.IsUndefined()) { return String::New(info.Env(), testValueUtf8); } else { - return String::New(info.Env(), testValueUtf8, length.Uint32Value()); + return String::New( + info.Env(), testValueUtf8, length.As().Uint32Value()); } } else if (encoding.Utf8Value() == "utf16") { if (length.IsUndefined()) { return String::New(info.Env(), testValueUtf16); } else { - return String::New(info.Env(), testValueUtf16, length.Uint32Value()); + return String::New( + info.Env(), testValueUtf16, length.As().Uint32Value()); } } else { Error::New(info.Env(), "Invalid encoding.").ThrowAsJavaScriptException(); @@ -41,15 +45,19 @@ Value CreateString(const CallbackInfo& info) { } } +Value CreateStringFromStringView(const CallbackInfo& info) { + return String::New(info.Env(), std::string_view("hello1")); +} + Value CheckString(const CallbackInfo& info) { String value = info[0].As(); String encoding = info[1].As(); - Number length = info[2].As(); + Value length = info[2]; if (encoding.Utf8Value() == "utf8") { std::string testValue = testValueUtf8; if (!length.IsUndefined()) { - testValue = testValue.substr(0, length.Uint32Value()); + testValue = testValue.substr(0, length.As().Uint32Value()); } std::string stringValue = value; @@ -57,7 +65,7 @@ Value CheckString(const CallbackInfo& info) { } else if (encoding.Utf8Value() == "utf16") { std::u16string testValue = testValueUtf16; if (!length.IsUndefined()) { - testValue = testValue.substr(0, length.Uint32Value()); + testValue = testValue.substr(0, length.As().Uint32Value()); } std::u16string stringValue = value; @@ -69,26 +77,47 @@ Value CheckString(const CallbackInfo& info) { } Value CreateSymbol(const CallbackInfo& info) { - String description = info[0].As(); + Value description = info[0]; if (!description.IsUndefined()) { - return Symbol::New(info.Env(), description); + return Symbol::New(info.Env(), description.As()); } else { return Symbol::New(info.Env()); } } +Value CreateSymbolFromStringView(const CallbackInfo& info) { + return Symbol::New(info.Env(), std::string_view("hello2")); +} + Value CheckSymbol(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].Type() == napi_symbol); } +void NullStringShouldThrow(const CallbackInfo& info) { + const char* nullStr = nullptr; + String::New(info.Env(), nullStr); +} + +void NullString16ShouldThrow(const CallbackInfo& info) { + const char16_t* nullStr = nullptr; + String::New(info.Env(), nullStr); +} + Object InitName(Env env) { Object exports = Object::New(env); exports["echoString"] = Function::New(env, EchoString); exports["createString"] = Function::New(env, CreateString); + exports["createStringFromStringView"] = + Function::New(env, CreateStringFromStringView); + exports["nullStringShouldThrow"] = Function::New(env, NullStringShouldThrow); + exports["nullString16ShouldThrow"] = + Function::New(env, NullString16ShouldThrow); exports["checkString"] = Function::New(env, CheckString); exports["createSymbol"] = Function::New(env, CreateSymbol); + exports["createSymbolFromStringView"] = + Function::New(env, CreateSymbolFromStringView); exports["checkSymbol"] = Function::New(env, CheckSymbol); return exports; diff --git a/test/name.js b/test/name.js index 4e9659312..8113565c8 100644 --- a/test/name.js +++ b/test/name.js @@ -1,13 +1,16 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { const expected = '123456789'; + assert.throws(binding.name.nullStringShouldThrow, { + name: 'Error', + message: 'Error in native callback' + }); assert.ok(binding.name.checkString(expected, 'utf8')); assert.ok(binding.name.checkString(expected, 'utf16')); assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf8', 3)); @@ -33,6 +36,7 @@ function test(binding) { assert.ok(binding.name.checkString(substr2, 'utf8', 3)); assert.ok(binding.name.checkString(substr2, 'utf16', 3)); + // eslint-disable-next-line symbol-description assert.ok(binding.name.checkSymbol(Symbol())); assert.ok(binding.name.checkSymbol(Symbol('test'))); @@ -52,4 +56,9 @@ function test(binding) { assert.strictEqual(binding.name.echoString(str, 'utf8'), str); assert.strictEqual(binding.name.echoString(str, 'utf16'), str); } + + assert.strictEqual(binding.name.createStringFromStringView(), 'hello1'); + const symFromStringView = binding.name.createSymbolFromStringView(); + assert.strictEqual(typeof symFromStringView, 'symbol'); + assert.strictEqual(symFromStringView.description, 'hello2'); } diff --git a/test/napi_child.js b/test/napi_child.js index 76f0bc56a..e3f89fd0b 100644 --- a/test/napi_child.js +++ b/test/napi_child.js @@ -1,12 +1,12 @@ // Makes sure that child processes are spawned appropriately. -exports.spawnSync = function(command, args, options) { +exports.spawnSync = function (command, args, options) { if (require('../index').needsFlag) { args.splice(0, 0, '--napi-modules'); } return require('child_process').spawnSync(command, args, options); }; -exports.spawn = function(command, args, options) { +exports.spawn = function (command, args, options) { if (require('../index').needsFlag) { args.splice(0, 0, '--napi-modules'); } diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index bd2488435..b05af20cc 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -1,27 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; +Value DeletePropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Number key = info[1].As(); + return Boolean::New(info.Env(), MaybeUnwrap(obj.Delete(key.Uint32Value()))); +} + Value DeletePropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Delete(static_cast(key)), false)); } Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(key), false)); } Value DeletePropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value().c_str()), false)); } Value DeletePropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value()), false)); } diff --git a/test/object/delete_property.js b/test/object/delete_property.js index 8c313d03e..fa8a5f132 100644 --- a/test/object/delete_property.js +++ b/test/object/delete_property.js @@ -1,15 +1,13 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function testDeleteProperty(nativeDeleteProperty) { +function test (binding) { + function testDeleteProperty (nativeDeleteProperty) { const obj = { one: 1, two: 2 }; - Object.defineProperty(obj, "three", {configurable: false, value: 3}); + Object.defineProperty(obj, 'three', { configurable: false, value: 3 }); assert.strictEqual(nativeDeleteProperty(obj, 'one'), true); assert.strictEqual(nativeDeleteProperty(obj, 'missing'), true); @@ -19,12 +17,18 @@ function test(binding) { assert.deepStrictEqual(obj, { two: 2 }); } - function testShouldThrowErrorIfKeyIsInvalid(nativeDeleteProperty) { + function testShouldThrowErrorIfKeyIsInvalid (nativeDeleteProperty) { assert.throws(() => { nativeDeleteProperty(undefined, 'test'); }, /Cannot convert undefined or null to object/); } + const testObj = { 15: 42, three: 3 }; + + binding.object.deletePropertyWithUint32(testObj, 15); + + assert.strictEqual(Object.prototype.hasOwnProperty.call(testObj, 15), false); + testDeleteProperty(binding.object.deletePropertyWithNapiValue); testDeleteProperty(binding.object.deletePropertyWithNapiWrapperValue); testDeleteProperty(binding.object.deletePropertyWithCStyleString); diff --git a/test/object/finalizer.cc b/test/object/finalizer.cc index 3518ae99d..6122e5eb9 100644 --- a/test/object/finalizer.cc +++ b/test/object/finalizer.cc @@ -7,23 +7,24 @@ static int dummy; Value AddFinalizer(const CallbackInfo& info) { ObjectReference* ref = new ObjectReference; *ref = Persistent(Object::New(info.Env())); - info[0] - .As() - .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref) { - ref->Set("finalizerCalled", true); - delete ref; - }, ref); + info[0].As().AddFinalizer( + [](Napi::Env /*env*/, ObjectReference* ref) { + ref->Set("finalizerCalled", true); + delete ref; + }, + ref); return ref->Value(); } Value AddFinalizerWithHint(const CallbackInfo& info) { ObjectReference* ref = new ObjectReference; *ref = Persistent(Object::New(info.Env())); - info[0] - .As() - .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) { - ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy); - delete ref; - }, ref, &dummy); + info[0].As().AddFinalizer( + [](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) { + ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy); + delete ref; + }, + ref, + &dummy); return ref->Value(); } diff --git a/test/object/finalizer.js b/test/object/finalizer.js index 312b2de6d..be6c98a32 100644 --- a/test/object/finalizer.js +++ b/test/object/finalizer.js @@ -1,17 +1,15 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('../testUtil'); -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); -function createWeakRef(binding, bindingToTest) { +function createWeakRef (binding, bindingToTest) { return binding.object[bindingToTest]({}); } -function test(binding) { +function test (binding) { let obj1; let obj2; return testUtil.runGCTests([ diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 0cdaa50d7..2791ad2aa 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -1,27 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value GetPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return obj.Get(static_cast(key)); + return MaybeUnwrapOr(obj.Get(static_cast(key)), Value()); } Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return obj.Get(key); + return MaybeUnwrapOr(obj.Get(key), Value()); +} + +Value GetPropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Number key = info[1].As(); + return MaybeUnwrap(obj.Get(key.Uint32Value())); } Value GetPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value().c_str()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value()), Value()); } diff --git a/test/object/get_property.js b/test/object/get_property.js index 7ec9b119e..fd5e98773 100644 --- a/test/object/get_property.js +++ b/test/object/get_property.js @@ -1,30 +1,40 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function testGetProperty(nativeGetProperty) { +function test (binding) { + function testGetProperty (nativeGetProperty) { const obj = { test: 1 }; assert.strictEqual(nativeGetProperty(obj, 'test'), 1); } - function testShouldThrowErrorIfKeyIsInvalid(nativeGetProperty) { + function testShouldReturnUndefinedIfKeyIsNotPresent (nativeGetProperty) { + const obj = { }; + assert.strictEqual(nativeGetProperty(obj, 'test'), undefined); + } + + function testShouldThrowErrorIfKeyIsInvalid (nativeGetProperty) { assert.throws(() => { nativeGetProperty(undefined, 'test'); }, /Cannot convert undefined or null to object/); } - testGetProperty(binding.object.getPropertyWithNapiValue); - testGetProperty(binding.object.getPropertyWithNapiWrapperValue); - testGetProperty(binding.object.getPropertyWithCStyleString); - testGetProperty(binding.object.getPropertyWithCppStyleString); + const testObject = { 42: 100 }; + const property = binding.object.getPropertyWithUint32(testObject, 42); + assert.strictEqual(property, 100); + + const nativeFunctions = [ + binding.object.getPropertyWithNapiValue, + binding.object.getPropertyWithNapiWrapperValue, + binding.object.getPropertyWithCStyleString, + binding.object.getPropertyWithCppStyleString + ]; - testShouldThrowErrorIfKeyIsInvalid(binding.object.getPropertyWithNapiValue); - testShouldThrowErrorIfKeyIsInvalid(binding.object.getPropertyWithNapiWrapperValue); - testShouldThrowErrorIfKeyIsInvalid(binding.object.getPropertyWithCStyleString); - testShouldThrowErrorIfKeyIsInvalid(binding.object.getPropertyWithCppStyleString); + nativeFunctions.forEach((nativeFunction) => { + testGetProperty(nativeFunction); + testShouldReturnUndefinedIfKeyIsNotPresent(nativeFunction); + testShouldThrowErrorIfKeyIsInvalid(nativeFunction); + }); } diff --git a/test/object/has_own_property.cc b/test/object/has_own_property.cc index 7351be2c0..b566fefbb 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -1,27 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(static_cast(key)), false)); } Value HasOwnPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(key)); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(key), false)); } Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value().c_str()), false)); } Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value()), false)); } diff --git a/test/object/has_own_property.js b/test/object/has_own_property.js index 570b0ee46..1315a47d9 100644 --- a/test/object/has_own_property.js +++ b/test/object/has_own_property.js @@ -1,13 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function testHasOwnProperty(nativeHasOwnProperty) { +function test (binding) { + function testHasOwnProperty (nativeHasOwnProperty) { const obj = { one: 1 }; Object.defineProperty(obj, 'two', { value: 2 }); @@ -18,7 +16,7 @@ function test(binding) { assert.strictEqual(nativeHasOwnProperty(obj, 'toString'), false); } - function testShouldThrowErrorIfKeyIsInvalid(nativeHasOwnProperty) { + function testShouldThrowErrorIfKeyIsInvalid (nativeHasOwnProperty) { assert.throws(() => { nativeHasOwnProperty(undefined, 'test'); }, /Cannot convert undefined or null to object/); diff --git a/test/object/has_property.cc b/test/object/has_property.cc index 0a1a45942..46c13de30 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -1,27 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(static_cast(key))); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Has(static_cast(key)), false)); } Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(key), false)); } Value HasPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value().c_str())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()), false)); +} + +Value HasPropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Number jsKey = info[1].As(); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Uint32Value()), false)); } Value HasPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value()), false)); } diff --git a/test/object/has_property.js b/test/object/has_property.js index a1b942dfb..f2b3a8e3c 100644 --- a/test/object/has_property.js +++ b/test/object/has_property.js @@ -1,13 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function testHasProperty(nativeHasProperty) { +function test (binding) { + function testHasProperty (nativeHasProperty) { const obj = { one: 1 }; Object.defineProperty(obj, 'two', { value: 2 }); @@ -18,12 +16,15 @@ function test(binding) { assert.strictEqual(nativeHasProperty(obj, 'toString'), true); } - function testShouldThrowErrorIfKeyIsInvalid(nativeHasProperty) { + function testShouldThrowErrorIfKeyIsInvalid (nativeHasProperty) { assert.throws(() => { nativeHasProperty(undefined, 'test'); }, /Cannot convert undefined or null to object/); } + const objectWithInt32Key = { 12: 101 }; + assert.strictEqual(binding.object.hasPropertyWithUint32(objectWithInt32Key, 12), true); + testHasProperty(binding.object.hasPropertyWithNapiValue); testHasProperty(binding.object.hasPropertyWithNapiWrapperValue); testHasProperty(binding.object.hasPropertyWithCStyleString); diff --git a/test/object/object.cc b/test/object/object.cc index 2c0ce420b..60aae768f 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -1,20 +1,24 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; // Native wrappers for testing Object::Get() +Value GetPropertyWithUint32(const CallbackInfo& info); Value GetPropertyWithNapiValue(const CallbackInfo& info); Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info); Value GetPropertyWithCStyleString(const CallbackInfo& info); Value GetPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Set() -void SetPropertyWithNapiValue(const CallbackInfo& info); -void SetPropertyWithNapiWrapperValue(const CallbackInfo& info); -void SetPropertyWithCStyleString(const CallbackInfo& info); -void SetPropertyWithCppStyleString(const CallbackInfo& info); +Value SetPropertyWithUint32(const CallbackInfo& info); +Value SetPropertyWithNapiValue(const CallbackInfo& info); +Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info); +Value SetPropertyWithCStyleString(const CallbackInfo& info); +Value SetPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Delete() +Value DeletePropertyWithUint32(const CallbackInfo& info); Value DeletePropertyWithNapiValue(const CallbackInfo& info); Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info); Value DeletePropertyWithCStyleString(const CallbackInfo& info); @@ -27,6 +31,7 @@ Value HasOwnPropertyWithCStyleString(const CallbackInfo& info); Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Has() +Value HasPropertyWithUint32(const CallbackInfo& info); Value HasPropertyWithNapiValue(const CallbackInfo& info); Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info); Value HasPropertyWithCStyleString(const CallbackInfo& info); @@ -36,6 +41,14 @@ Value HasPropertyWithCppStyleString(const CallbackInfo& info); Value AddFinalizer(const CallbackInfo& info); Value AddFinalizerWithHint(const CallbackInfo& info); +// Native wrappers for testing Object::operator [] +Value SubscriptGetWithCStyleString(const CallbackInfo& info); +Value SubscriptGetWithCppStyleString(const CallbackInfo& info); +Value SubscriptGetAtIndex(const CallbackInfo& info); +void SubscriptSetWithCStyleString(const CallbackInfo& info); +void SubscriptSetWithCppStyleString(const CallbackInfo& info); +void SubscriptSetAtIndex(const CallbackInfo& info); + static bool testValue = true; // Used to test void* Data() integrity struct UserDataHolder { @@ -43,11 +56,11 @@ struct UserDataHolder { }; Value TestGetter(const CallbackInfo& info) { - return Boolean::New(info.Env(), testValue); + return Boolean::New(info.Env(), testValue); } void TestSetter(const CallbackInfo& info) { - testValue = info[0].As(); + testValue = info[0].As(); } Value TestGetterWithUserData(const CallbackInfo& info) { @@ -61,7 +74,7 @@ void TestSetterWithUserData(const CallbackInfo& info) { } Value TestFunction(const CallbackInfo& info) { - return Boolean::New(info.Env(), true); + return Boolean::New(info.Env(), true); } Value TestFunctionWithUserData(const CallbackInfo& info) { @@ -69,9 +82,22 @@ Value TestFunctionWithUserData(const CallbackInfo& info) { return Number::New(info.Env(), holder->value); } +Value EmptyConstructor(const CallbackInfo& info) { + auto env = info.Env(); + bool isEmpty = info[0].As(); + Object object = isEmpty ? Object() : Object(env, Object::New(env)); + return Boolean::New(env, object.IsEmpty()); +} + +Value ConstructorFromObject(const CallbackInfo& info) { + auto env = info.Env(); + Object object = info[0].As(); + return Object(env, object); +} + Array GetPropertyNames(const CallbackInfo& info) { Object obj = info[0].As(); - Array arr = obj.GetPropertyNames(); + Array arr = MaybeUnwrap(obj.GetPropertyNames()); return arr; } @@ -86,36 +112,56 @@ void DefineProperties(const CallbackInfo& info) { if (nameType.Utf8Value() == "literal") { obj.DefineProperties({ - PropertyDescriptor::Accessor(env, obj, "readonlyAccessor", TestGetter), - PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), - PropertyDescriptor::Accessor(env, obj, "readonlyAccessorWithUserData", TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - PropertyDescriptor::Accessor(env, obj, "readwriteAccessorWithUserData", TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - - PropertyDescriptor::Accessor("readonlyAccessorT"), - PropertyDescriptor::Accessor( - "readwriteAccessorT"), - PropertyDescriptor::Accessor( - "readonlyAccessorWithUserDataT", - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - PropertyDescriptor::Accessor< - TestGetterWithUserData, - TestSetterWithUserData>("readwriteAccessorWithUserDataT", - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - - PropertyDescriptor::Value("readonlyValue", trueValue), - PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), - PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), - PropertyDescriptor::Value("configurableValue", trueValue, napi_configurable), - PropertyDescriptor::Function(env, obj, "function", TestFunction), - PropertyDescriptor::Function(env, obj, "functionWithUserData", TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, obj, "readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor( + env, obj, "readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, + obj, + "readonlyAccessorWithUserData", + TestGetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, + obj, + "readwriteAccessorWithUserData", + TestGetterWithUserData, + TestSetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Accessor("readonlyAccessorT"), + PropertyDescriptor::Accessor( + "readwriteAccessorT"), + PropertyDescriptor::Accessor( + "readonlyAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor( + "readwriteAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Value("readonlyValue", trueValue), + PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), + PropertyDescriptor::Value( + "enumerableValue", trueValue, napi_enumerable), + PropertyDescriptor::Value( + "configurableValue", trueValue, napi_configurable), + PropertyDescriptor::Function(env, obj, "function", TestFunction), + PropertyDescriptor::Function(env, + obj, + "functionWithUserData", + TestFunctionWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), }); } else if (nameType.Utf8Value() == "string") { - // VS2013 has lifetime issues when passing temporary objects into the constructor of another - // object. It generates code to destruct the object as soon as the constructor call returns. - // Since this isn't a common case for using std::string objects, I'm refactoring the test to - // work around the issue. + // VS2013 has lifetime issues when passing temporary objects into the + // constructor of another object. It generates code to destruct the object + // as soon as the constructor call returns. Since this isn't a common case + // for using std::string objects, I'm refactoring the test to work around + // the issue. std::string str1("readonlyAccessor"); std::string str2("readwriteAccessor"); std::string str1a("readonlyAccessorWithUserData"); @@ -134,66 +180,105 @@ void DefineProperties(const CallbackInfo& info) { std::string str8("functionWithUserData"); obj.DefineProperties({ - PropertyDescriptor::Accessor(env, obj, str1, TestGetter), - PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), - PropertyDescriptor::Accessor(env, obj, str1a, TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - PropertyDescriptor::Accessor(env, obj, str2a, TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - - PropertyDescriptor::Accessor(str1t), - PropertyDescriptor::Accessor(str2t), - PropertyDescriptor::Accessor(str1at, - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - PropertyDescriptor::Accessor< - TestGetterWithUserData, - TestSetterWithUserData>(str2at, - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - - PropertyDescriptor::Value(str3, trueValue), - PropertyDescriptor::Value(str4, trueValue, napi_writable), - PropertyDescriptor::Value(str5, trueValue, napi_enumerable), - PropertyDescriptor::Value(str6, trueValue, napi_configurable), - PropertyDescriptor::Function(env, obj, str7, TestFunction), - PropertyDescriptor::Function(env, obj, str8, TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, obj, str1, TestGetter), + PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, + obj, + str1a, + TestGetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, + obj, + str2a, + TestGetterWithUserData, + TestSetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Accessor(str1t), + PropertyDescriptor::Accessor(str2t), + PropertyDescriptor::Accessor( + str1at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor( + str2at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Value(str3, trueValue), + PropertyDescriptor::Value(str4, trueValue, napi_writable), + PropertyDescriptor::Value(str5, trueValue, napi_enumerable), + PropertyDescriptor::Value(str6, trueValue, napi_configurable), + PropertyDescriptor::Function(env, obj, str7, TestFunction), + PropertyDescriptor::Function(env, + obj, + str8, + TestFunctionWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ - PropertyDescriptor::Accessor(env, obj, - Napi::String::New(env, "readonlyAccessor"), TestGetter), - PropertyDescriptor::Accessor(env, obj, - Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), - PropertyDescriptor::Accessor(env, obj, - Napi::String::New(env, "readonlyAccessorWithUserData"), TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - PropertyDescriptor::Accessor(env, obj, - Napi::String::New(env, "readwriteAccessorWithUserData"), TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), - - PropertyDescriptor::Accessor( - Napi::String::New(env, "readonlyAccessorT")), - PropertyDescriptor::Accessor( - Napi::String::New(env, "readwriteAccessorT")), - PropertyDescriptor::Accessor( - Napi::String::New(env, "readonlyAccessorWithUserDataT"), - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - PropertyDescriptor::Accessor< - TestGetterWithUserData, TestSetterWithUserData>( - Napi::String::New(env, "readwriteAccessorWithUserDataT"), - napi_property_attributes::napi_default, - reinterpret_cast(holder)), - - PropertyDescriptor::Value( - Napi::String::New(env, "readonlyValue"), trueValue), - PropertyDescriptor::Value( - Napi::String::New(env, "readwriteValue"), trueValue, napi_writable), - PropertyDescriptor::Value( - Napi::String::New(env, "enumerableValue"), trueValue, napi_enumerable), - PropertyDescriptor::Value( - Napi::String::New(env, "configurableValue"), trueValue, napi_configurable), - PropertyDescriptor::Function(env, obj, - Napi::String::New(env, "function"), TestFunction), - PropertyDescriptor::Function(env, obj, - Napi::String::New(env, "functionWithUserData"), TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor( + env, obj, Napi::String::New(env, "readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor( + env, + obj, + Napi::String::New(env, "readwriteAccessor"), + TestGetter, + TestSetter), + PropertyDescriptor::Accessor( + env, + obj, + Napi::String::New(env, "readonlyAccessorWithUserData"), + TestGetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor( + env, + obj, + Napi::String::New(env, "readwriteAccessorWithUserData"), + TestGetterWithUserData, + TestSetterWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + + PropertyDescriptor::Value(Napi::String::New(env, "readonlyValue"), + trueValue), + PropertyDescriptor::Value( + Napi::String::New(env, "readwriteValue"), trueValue, napi_writable), + PropertyDescriptor::Value(Napi::String::New(env, "enumerableValue"), + trueValue, + napi_enumerable), + PropertyDescriptor::Value(Napi::String::New(env, "configurableValue"), + trueValue, + napi_configurable), + PropertyDescriptor::Function( + env, obj, Napi::String::New(env, "function"), TestFunction), + PropertyDescriptor::Function( + env, + obj, + Napi::String::New(env, "functionWithUserData"), + TestFunctionWithUserData, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), }); } } @@ -228,42 +313,136 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) { return obj; } -Object InitObject(Env env) { - Object exports = Object::New(env); +#ifdef NAPI_CPP_EXCEPTIONS +Value Sum(const CallbackInfo& info) { + Object object = info[0].As(); + int64_t sum = 0; - exports["GetPropertyNames"] = Function::New(env, GetPropertyNames); - exports["defineProperties"] = Function::New(env, DefineProperties); - exports["defineValueProperty"] = Function::New(env, DefineValueProperty); + for (const auto& e : object) { + sum += static_cast(e.second).As().Int64Value(); + } + + return Number::New(info.Env(), sum); +} + +void Increment(const CallbackInfo& info) { + Env env = info.Env(); + Object object = info[0].As(); + + for (auto e : object) { + int64_t value = static_cast(e.second).As().Int64Value(); + ++value; + e.second = Napi::Number::New(env, value); + } +} +#endif // NAPI_CPP_EXCEPTIONS - exports["getPropertyWithNapiValue"] = Function::New(env, GetPropertyWithNapiValue); - exports["getPropertyWithNapiWrapperValue"] = Function::New(env, GetPropertyWithNapiWrapperValue); - exports["getPropertyWithCStyleString"] = Function::New(env, GetPropertyWithCStyleString); - exports["getPropertyWithCppStyleString"] = Function::New(env, GetPropertyWithCppStyleString); +Value InstanceOf(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Function constructor = info[1].As(); + return Boolean::New(info.Env(), MaybeUnwrap(obj.InstanceOf(constructor))); +} + +Value GetPrototype(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + return MaybeUnwrap(obj.GetPrototype()); +} - exports["setPropertyWithNapiValue"] = Function::New(env, SetPropertyWithNapiValue); - exports["setPropertyWithNapiWrapperValue"] = Function::New(env, SetPropertyWithNapiWrapperValue); - exports["setPropertyWithCStyleString"] = Function::New(env, SetPropertyWithCStyleString); - exports["setPropertyWithCppStyleString"] = Function::New(env, SetPropertyWithCppStyleString); +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +Value SetPrototype(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Object prototype = info[1].UnsafeAs(); + return Boolean::New(info.Env(), MaybeUnwrap(obj.SetPrototype(prototype))); +} +#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE - exports["deletePropertyWithNapiValue"] = Function::New(env, DeletePropertyWithNapiValue); - exports["deletePropertyWithNapiWrapperValue"] = Function::New(env, DeletePropertyWithNapiWrapperValue); - exports["deletePropertyWithCStyleString"] = Function::New(env, DeletePropertyWithCStyleString); - exports["deletePropertyWithCppStyleString"] = Function::New(env, DeletePropertyWithCppStyleString); +Object InitObject(Env env) { + Object exports = Object::New(env); - exports["hasOwnPropertyWithNapiValue"] = Function::New(env, HasOwnPropertyWithNapiValue); - exports["hasOwnPropertyWithNapiWrapperValue"] = Function::New(env, HasOwnPropertyWithNapiWrapperValue); - exports["hasOwnPropertyWithCStyleString"] = Function::New(env, HasOwnPropertyWithCStyleString); - exports["hasOwnPropertyWithCppStyleString"] = Function::New(env, HasOwnPropertyWithCppStyleString); + exports["emptyConstructor"] = Function::New(env, EmptyConstructor); + exports["constructorFromObject"] = Function::New(env, ConstructorFromObject); - exports["hasPropertyWithNapiValue"] = Function::New(env, HasPropertyWithNapiValue); - exports["hasPropertyWithNapiWrapperValue"] = Function::New(env, HasPropertyWithNapiWrapperValue); - exports["hasPropertyWithCStyleString"] = Function::New(env, HasPropertyWithCStyleString); - exports["hasPropertyWithCppStyleString"] = Function::New(env, HasPropertyWithCppStyleString); + exports["GetPropertyNames"] = Function::New(env, GetPropertyNames); + exports["defineProperties"] = Function::New(env, DefineProperties); + exports["defineValueProperty"] = Function::New(env, DefineValueProperty); - exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic); + exports["getPropertyWithUint32"] = Function::New(env, GetPropertyWithUint32); + exports["getPropertyWithNapiValue"] = + Function::New(env, GetPropertyWithNapiValue); + exports["getPropertyWithNapiWrapperValue"] = + Function::New(env, GetPropertyWithNapiWrapperValue); + exports["getPropertyWithCStyleString"] = + Function::New(env, GetPropertyWithCStyleString); + exports["getPropertyWithCppStyleString"] = + Function::New(env, GetPropertyWithCppStyleString); + + exports["setPropertyWithUint32"] = Function::New(env, SetPropertyWithUint32); + exports["setPropertyWithNapiValue"] = + Function::New(env, SetPropertyWithNapiValue); + exports["setPropertyWithNapiWrapperValue"] = + Function::New(env, SetPropertyWithNapiWrapperValue); + exports["setPropertyWithCStyleString"] = + Function::New(env, SetPropertyWithCStyleString); + exports["setPropertyWithCppStyleString"] = + Function::New(env, SetPropertyWithCppStyleString); + + exports["deletePropertyWithUint32"] = + Function::New(env, DeletePropertyWithUint32); + exports["deletePropertyWithNapiValue"] = + Function::New(env, DeletePropertyWithNapiValue); + exports["deletePropertyWithNapiWrapperValue"] = + Function::New(env, DeletePropertyWithNapiWrapperValue); + exports["deletePropertyWithCStyleString"] = + Function::New(env, DeletePropertyWithCStyleString); + exports["deletePropertyWithCppStyleString"] = + Function::New(env, DeletePropertyWithCppStyleString); + + exports["hasOwnPropertyWithNapiValue"] = + Function::New(env, HasOwnPropertyWithNapiValue); + exports["hasOwnPropertyWithNapiWrapperValue"] = + Function::New(env, HasOwnPropertyWithNapiWrapperValue); + exports["hasOwnPropertyWithCStyleString"] = + Function::New(env, HasOwnPropertyWithCStyleString); + exports["hasOwnPropertyWithCppStyleString"] = + Function::New(env, HasOwnPropertyWithCppStyleString); + + exports["hasPropertyWithUint32"] = Function::New(env, HasPropertyWithUint32); + exports["hasPropertyWithNapiValue"] = + Function::New(env, HasPropertyWithNapiValue); + exports["hasPropertyWithNapiWrapperValue"] = + Function::New(env, HasPropertyWithNapiWrapperValue); + exports["hasPropertyWithCStyleString"] = + Function::New(env, HasPropertyWithCStyleString); + exports["hasPropertyWithCppStyleString"] = + Function::New(env, HasPropertyWithCppStyleString); + + exports["createObjectUsingMagic"] = + Function::New(env, CreateObjectUsingMagic); +#ifdef NAPI_CPP_EXCEPTIONS + exports["sum"] = Function::New(env, Sum); + exports["increment"] = Function::New(env, Increment); +#endif // NAPI_CPP_EXCEPTIONS exports["addFinalizer"] = Function::New(env, AddFinalizer); exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint); + exports["instanceOf"] = Function::New(env, InstanceOf); + + exports["subscriptGetWithCStyleString"] = + Function::New(env, SubscriptGetWithCStyleString); + exports["subscriptGetWithCppStyleString"] = + Function::New(env, SubscriptGetWithCppStyleString); + exports["subscriptGetAtIndex"] = Function::New(env, SubscriptGetAtIndex); + exports["subscriptSetWithCStyleString"] = + Function::New(env, SubscriptSetWithCStyleString); + exports["subscriptSetWithCppStyleString"] = + Function::New(env, SubscriptSetWithCppStyleString); + exports["subscriptSetAtIndex"] = Function::New(env, SubscriptSetAtIndex); + + exports["getPrototype"] = Function::New(env, GetPrototype); +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + exports["setPrototype"] = Function::New(env, SetPrototype); +#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + return exports; } diff --git a/test/object/object.js b/test/object/object.js index 8741e27f1..13488940c 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -1,24 +1,23 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function assertPropertyIs(obj, key, attribute) { +function test (binding) { + function assertPropertyIs (obj, key, attribute) { const propDesc = Object.getOwnPropertyDescriptor(obj, key); assert.ok(propDesc); assert.ok(propDesc[attribute]); } - function assertPropertyIsNot(obj, key, attribute) { + function assertPropertyIsNot (obj, key, attribute) { const propDesc = Object.getOwnPropertyDescriptor(obj, key); assert.ok(propDesc); assert.ok(!propDesc[attribute]); } - function testDefineProperties(nameType) { + function testDefineProperties (nameType) { const obj = {}; binding.object.defineProperties(obj, nameType); @@ -102,17 +101,30 @@ function test(binding) { testDefineProperties('string'); testDefineProperties('value'); + // eslint-disable-next-line no-lone-blocks + { + assert.strictEqual(binding.object.emptyConstructor(true), true); + assert.strictEqual(binding.object.emptyConstructor(false), false); + } + + { + const expected = { one: 1, two: 2, three: 3 }; + const actual = binding.object.constructorFromObject(expected); + assert.deepStrictEqual(actual, expected); + } + { const obj = {}; - const testSym = Symbol(); + const testSym = Symbol('testSym'); binding.object.defineValueProperty(obj, testSym, 1); assert.strictEqual(obj[testSym], 1); } { - const obj = {'one': 1, 'two': 2, 'three': 3}; - var arr = binding.object.GetPropertyNames(obj); - assert.deepStrictEqual(arr, ['one', 'two', 'three']) + const testSym = Symbol('testSym'); + const obj = { one: 1, two: 2, three: 3, [testSym]: 4 }; + const arr = binding.object.GetPropertyNames(obj); + assert.deepStrictEqual(arr, ['one', 'two', 'three']); } { @@ -136,4 +148,83 @@ function test(binding) { circular2: magicObject }); } + + { + function Ctor () {} + + assert.strictEqual(binding.object.instanceOf(new Ctor(), Ctor), true); + assert.strictEqual(binding.object.instanceOf(new Ctor(), Object), true); + assert.strictEqual(binding.object.instanceOf({}, Ctor), false); + assert.strictEqual(binding.object.instanceOf(null, Ctor), false); + } + + if ('sum' in binding.object) { + { + const obj = { + '-forbid': -0x4B1D, + '-feedcode': -0xFEEDC0DE, + '+office': +0x0FF1CE, + '+forbid': +0x4B1D, + '+deadbeef': +0xDEADBEEF, + '+feedcode': +0xFEEDC0DE + }; + + let sum = 0; + for (const key in obj) { + sum += obj[key]; + } + + assert.strictEqual(binding.object.sum(obj), sum); + } + + { + const obj = new Proxy({ + '-forbid': -0x4B1D, + '-feedcode': -0xFEEDC0DE, + '+office': +0x0FF1CE, + '+forbid': +0x4B1D, + '+deadbeef': +0xDEADBEEF, + '+feedcode': +0xFEEDC0DE + }, { + getOwnPropertyDescriptor (target, p) { + throw new Error('getOwnPropertyDescriptor error'); + }, + ownKeys (target) { + throw new Error('ownKeys error'); + } + }); + + assert.throws(() => { + binding.object.sum(obj); + }, /ownKeys error/); + } + } + + if ('increment' in binding.object) { + const obj = { + a: 0, + b: 1, + c: 2 + }; + + binding.object.increment(obj); + + assert.deepStrictEqual(obj, { + a: 1, + b: 2, + c: 3 + }); + } + + for (const prototype of [null, {}, Object.prototype]) { + const obj = Object.create(prototype); + assert.strictEqual(binding.object.getPrototype(obj), prototype); + } + + if ('setPrototype' in binding.object) { + const prototype = {}; + const obj = Object.create(null); + assert.strictEqual(binding.object.setPrototype(obj, prototype), true); + assert.strictEqual(Object.getPrototypeOf(obj), prototype); + } } diff --git a/test/object/object_deprecated.cc b/test/object/object_deprecated.cc index 2ec16e579..e5b9f01b7 100644 --- a/test/object/object_deprecated.cc +++ b/test/object/object_deprecated.cc @@ -7,15 +7,15 @@ static bool testValue = true; namespace { Value TestGetter(const CallbackInfo& info) { - return Boolean::New(info.Env(), testValue); + return Boolean::New(info.Env(), testValue); } void TestSetter(const CallbackInfo& info) { - testValue = info[0].As(); + testValue = info[0].As(); } Value TestFunction(const CallbackInfo& info) { - return Boolean::New(info.Env(), true); + return Boolean::New(info.Env(), true); } void DefineProperties(const CallbackInfo& info) { @@ -25,37 +25,41 @@ void DefineProperties(const CallbackInfo& info) { if (nameType.Utf8Value() == "literal") { obj.DefineProperties({ - PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), - PropertyDescriptor::Accessor("readwriteAccessor", TestGetter, TestSetter), - PropertyDescriptor::Function("function", TestFunction), + PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor( + "readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Function("function", TestFunction), }); } else if (nameType.Utf8Value() == "string") { - // VS2013 has lifetime issues when passing temporary objects into the constructor of another - // object. It generates code to destruct the object as soon as the constructor call returns. - // Since this isn't a common case for using std::string objects, I'm refactoring the test to - // work around the issue. + // VS2013 has lifetime issues when passing temporary objects into the + // constructor of another object. It generates code to destruct the object + // as soon as the constructor call returns. Since this isn't a common case + // for using std::string objects, I'm refactoring the test to work around + // the issue. std::string str1("readonlyAccessor"); std::string str2("readwriteAccessor"); std::string str7("function"); obj.DefineProperties({ - PropertyDescriptor::Accessor(str1, TestGetter), - PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), - PropertyDescriptor::Function(str7, TestFunction), + PropertyDescriptor::Accessor(str1, TestGetter), + PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), + PropertyDescriptor::Function(str7, TestFunction), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ - PropertyDescriptor::Accessor( - Napi::String::New(env, "readonlyAccessor"), TestGetter), - PropertyDescriptor::Accessor( - Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), - PropertyDescriptor::Function( - Napi::String::New(env, "function"), TestFunction), + PropertyDescriptor::Accessor(Napi::String::New(env, "readonlyAccessor"), + TestGetter), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessor"), + TestGetter, + TestSetter), + PropertyDescriptor::Function(Napi::String::New(env, "function"), + TestFunction), }); } } -} // end of anonymous namespace +} // end of anonymous namespace Object InitObjectDeprecated(Env env) { Object exports = Object::New(env); diff --git a/test/object/object_deprecated.js b/test/object/object_deprecated.js index 153fb11e1..06cc0998c 100644 --- a/test/object/object_deprecated.js +++ b/test/object/object_deprecated.js @@ -1,27 +1,21 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { +function test (binding) { if (!('object_deprecated' in binding)) { return; } - function assertPropertyIs(obj, key, attribute) { - const propDesc = Object.getOwnPropertyDescriptor(obj, key); - assert.ok(propDesc); - assert.ok(propDesc[attribute]); - } - function assertPropertyIsNot(obj, key, attribute) { + function assertPropertyIsNot (obj, key, attribute) { const propDesc = Object.getOwnPropertyDescriptor(obj, key); assert.ok(propDesc); assert.ok(!propDesc[attribute]); } - function testDefineProperties(nameType) { + function testDefineProperties (nameType) { const obj = {}; binding.object.defineProperties(obj, nameType); diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc new file mode 100644 index 000000000..40aaeb467 --- /dev/null +++ b/test/object/object_freeze_seal.cc @@ -0,0 +1,25 @@ +#include "napi.h" +#include "test_helper.h" + +#if (NAPI_VERSION > 7) + +using namespace Napi; + +Value Freeze(const CallbackInfo& info) { + Object obj = info[0].As(); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Freeze(), false)); +} + +Value Seal(const CallbackInfo& info) { + Object obj = info[0].As(); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Seal(), false)); +} + +Object InitObjectFreezeSeal(Env env) { + Object exports = Object::New(env); + exports["freeze"] = Function::New(env, Freeze); + exports["seal"] = Function::New(env, Seal); + return exports; +} + +#endif diff --git a/test/object/object_freeze_seal.js b/test/object/object_freeze_seal.js new file mode 100644 index 000000000..667af6a6f --- /dev/null +++ b/test/object/object_freeze_seal.js @@ -0,0 +1,61 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + { + const obj = { x: 'a', y: 'b', z: 'c' }; + assert.strictEqual(binding.object_freeze_seal.freeze(obj), true); + assert.strictEqual(Object.isFrozen(obj), true); + assert.throws(() => { + obj.x = 10; + }, /Cannot assign to read only property 'x' of object '#/); + assert.throws(() => { + obj.w = 15; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + } + + { + const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { + preventExtensions () { + throw new Error('foo'); + } + }); + + assert.throws(() => { + binding.object_freeze_seal.freeze(obj); + }, /foo/); + } + + { + const obj = { x: 'a', y: 'b', z: 'c' }; + assert.strictEqual(binding.object_freeze_seal.seal(obj), true); + assert.strictEqual(Object.isSealed(obj), true); + assert.throws(() => { + obj.w = 'd'; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + // Sealed objects allow updating existing properties, + // so this should not throw. + obj.x = 'd'; + } + + { + const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { + preventExtensions () { + throw new Error('foo'); + } + }); + + assert.throws(() => { + binding.object_freeze_seal.seal(obj); + }, /foo/); + } +} diff --git a/test/object/set_property.cc b/test/object/set_property.cc index 19ab245b4..da8c93bbd 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -1,31 +1,45 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; -void SetPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); +Value SetPropertyWithNapiValue(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); Value value = info[2]; - obj.Set(static_cast(key), value); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(static_cast(key), value), false)); } -void SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); +Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); Value value = info[2]; - obj.Set(key, value); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Set(key, value), false)); } -void SetPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); +Value SetPropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Number key = info[1].As(); + Value value = info[2]; + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Set(key.Uint32Value(), value), false)); +} + +Value SetPropertyWithCStyleString(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); Value value = info[2]; - obj.Set(jsKey.Utf8Value().c_str(), value); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value().c_str(), value), false)); } -void SetPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); +Value SetPropertyWithCppStyleString(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); Value value = info[2]; - obj.Set(jsKey.Utf8Value(), value); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); } diff --git a/test/object/set_property.js b/test/object/set_property.js index 9b64cc5dd..b2a5fa12a 100644 --- a/test/object/set_property.js +++ b/test/object/set_property.js @@ -1,19 +1,17 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); -function test(binding) { - function testSetProperty(nativeSetProperty) { +function test (binding) { + function testSetProperty (nativeSetProperty, key = 'test') { const obj = {}; - nativeSetProperty(obj, 'test', 1); - assert.strictEqual(obj.test, 1); + assert.strictEqual(nativeSetProperty(obj, key, 1), true); + assert.strictEqual(obj[key], 1); } - function testShouldThrowErrorIfKeyIsInvalid(nativeSetProperty) { + function testShouldThrowErrorIfKeyIsInvalid (nativeSetProperty) { assert.throws(() => { nativeSetProperty(undefined, 'test', 1); }, /Cannot convert undefined or null to object/); @@ -23,6 +21,7 @@ function test(binding) { testSetProperty(binding.object.setPropertyWithNapiWrapperValue); testSetProperty(binding.object.setPropertyWithCStyleString); testSetProperty(binding.object.setPropertyWithCppStyleString); + testSetProperty(binding.object.setPropertyWithUint32, 12); testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiValue); testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiWrapperValue); diff --git a/test/object/subscript_operator.cc b/test/object/subscript_operator.cc new file mode 100644 index 000000000..15bb74620 --- /dev/null +++ b/test/object/subscript_operator.cc @@ -0,0 +1,58 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +Value SubscriptGetWithCStyleString(const CallbackInfo& info) { + String jsKey = info[1].As(); + + // make sure const case compiles + const Object obj2 = info[0].As(); + MaybeUnwrap(obj2[jsKey.Utf8Value().c_str()]).As(); + + Object obj = info[0].As(); + return obj[jsKey.Utf8Value().c_str()]; +} + +Value SubscriptGetWithCppStyleString(const CallbackInfo& info) { + String jsKey = info[1].As(); + + // make sure const case compiles + const Object obj2 = info[0].As(); + MaybeUnwrap(obj2[jsKey.Utf8Value()]).As(); + + Object obj = info[0].As(); + return obj[jsKey.Utf8Value()]; +} + +Value SubscriptGetAtIndex(const CallbackInfo& info) { + uint32_t index = info[1].As(); + + // make sure const case compiles + const Object obj2 = info[0].As(); + MaybeUnwrap(obj2[index]).As(); + + Object obj = info[0].As(); + return obj[index]; +} + +void SubscriptSetWithCStyleString(const CallbackInfo& info) { + Object obj = info[0].As(); + String jsKey = info[1].As(); + Value value = info[2]; + obj[jsKey.Utf8Value().c_str()] = value; +} + +void SubscriptSetWithCppStyleString(const CallbackInfo& info) { + Object obj = info[0].As(); + String jsKey = info[1].As(); + Value value = info[2]; + obj[jsKey.Utf8Value()] = value; +} + +void SubscriptSetAtIndex(const CallbackInfo& info) { + Object obj = info[0].As(); + uint32_t index = info[1].As(); + Value value = info[2]; + obj[index] = value; +} diff --git a/test/object/subscript_operator.js b/test/object/subscript_operator.js new file mode 100644 index 000000000..4d0c544f3 --- /dev/null +++ b/test/object/subscript_operator.js @@ -0,0 +1,17 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test (binding) { + function testProperty (obj, key, value, nativeGetProperty, nativeSetProperty) { + nativeSetProperty(obj, key, value); + assert.strictEqual(nativeGetProperty(obj, key), value); + } + + testProperty({}, 'key', 'value', binding.object.subscriptGetWithCStyleString, binding.object.subscriptSetWithCStyleString); + testProperty({ key: 'override me' }, 'key', 'value', binding.object.subscriptGetWithCppStyleString, binding.object.subscriptSetWithCppStyleString); + testProperty({}, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex); + testProperty({ key: 'override me' }, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex); +} diff --git a/test/object_reference.cc b/test/object_reference.cc new file mode 100644 index 000000000..691b5f63d --- /dev/null +++ b/test/object_reference.cc @@ -0,0 +1,437 @@ +/* ObjectReference can be used to create references to Values that +are not Objects by creating a blank Object and setting Values to +it. Subclasses of Objects can only be set using an ObjectReference +by first casting it as an Object. */ +#include "assert.h" +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +ObjectReference weak; +ObjectReference persistent; +ObjectReference reference; + +ObjectReference casted_weak; +ObjectReference casted_persistent; +ObjectReference casted_reference; + +// Set keys can be one of: +// C style string, std::string& utf8, and const char * + +// Set values can be one of: +// Napi::Value +// napi_value (req static_cast) +// const char* (c style string) +// boolean +// double + +enum VAL_TYPES { JS = 0, C_STR, CPP_STR, BOOL, INT, DOUBLE, JS_CAST }; + +// Test that Set() with std::string key and value accepts temporaries (rvalues). +// This verifies that the parameter is `const std::string&` rather than +// `std::string&`. +void SetWithTempString(const Napi::CallbackInfo& info) { + Env env = info.Env(); + HandleScope scope(env); + + Napi::ObjectReference ref = Persistent(Object::New(env)); + ref.SuppressDestruct(); + + ref.Set(std::string("tempKey"), std::string("tempValue")); + ref.Set(std::string("anotherKey"), info[0].As().Utf8Value()); + + assert(MaybeUnwrap(ref.Get("tempKey")).As().Utf8Value() == + "tempValue"); + assert(MaybeUnwrap(ref.Get("anotherKey")).As().Utf8Value() == + info[0].As().Utf8Value()); +} + +void MoveOperatorsTest(const Napi::CallbackInfo& info) { + Napi::ObjectReference existingRef; + Napi::ObjectReference existingRef2; + Napi::Object testObject = Napi::Object::New(info.Env()); + testObject.Set("testProp", "tProp"); + + // ObjectReference(Reference&& other); + Napi::Reference refObj = + Napi::Reference::New(testObject); + Napi::ObjectReference objRef = std::move(refObj); + std::string prop = MaybeUnwrap(objRef.Get("testProp")).As(); + assert(prop == "tProp"); + + // ObjectReference& operator=(Reference&& other); + Napi::Reference refObj2 = + Napi::Reference::New(testObject); + existingRef = std::move(refObj2); + prop = MaybeUnwrap(existingRef.Get("testProp")).As(); + assert(prop == "tProp"); + + // ObjectReference(ObjectReference&& other); + Napi::ObjectReference objRef3 = std::move(existingRef); + prop = MaybeUnwrap(objRef3.Get("testProp")).As(); + assert(prop == "tProp"); + + // ObjectReference& operator=(ObjectReference&& other); + existingRef2 = std::move(objRef3); + prop = MaybeUnwrap(objRef.Get("testProp")).As(); + assert(prop == "tProp"); +} + +void SetObjectWithCStringKey(Napi::ObjectReference& obj, + Napi::Value key, + Napi::Value val, + int valType) { + std::string c_key = key.As().Utf8Value(); + switch (valType) { + case JS: + obj.Set(c_key.c_str(), val); + break; + + case JS_CAST: + obj.Set(c_key.c_str(), static_cast(val)); + break; + + case C_STR: { + std::string c_val = val.As().Utf8Value(); + obj.Set(c_key.c_str(), c_val.c_str()); + break; + } + + case BOOL: + obj.Set(c_key.c_str(), val.As().Value()); + break; + + case DOUBLE: + obj.Set(c_key.c_str(), val.As().DoubleValue()); + break; + } +} + +void SetObjectWithCppStringKey(Napi::ObjectReference& obj, + Napi::Value key, + Napi::Value val, + int valType) { + std::string c_key = key.As(); + switch (valType) { + case JS: + obj.Set(c_key, val); + break; + + case JS_CAST: + obj.Set(c_key, static_cast(val)); + break; + + case CPP_STR: { + std::string c_val = val.As(); + obj.Set(c_key, c_val); + break; + } + + case BOOL: + obj.Set(c_key, val.As().Value()); + break; + + case DOUBLE: + obj.Set(c_key, val.As().DoubleValue()); + break; + } +} + +void SetObjectWithIntKey(Napi::ObjectReference& obj, + Napi::Value key, + Napi::Value val, + int valType) { + uint32_t c_key = key.As().Uint32Value(); + switch (valType) { + case JS: + obj.Set(c_key, val); + break; + + case JS_CAST: + obj.Set(c_key, static_cast(val)); + break; + + case C_STR: { + std::string c_val = val.As(); + obj.Set(c_key, c_val.c_str()); + break; + } + + case CPP_STR: { + std::string cpp_val = val.As(); + obj.Set(c_key, cpp_val); + break; + } + + case BOOL: + obj.Set(c_key, val.As().Value()); + break; + + case DOUBLE: + obj.Set(c_key, val.As().DoubleValue()); + break; + } +} + +void SetObject(const Napi::CallbackInfo& info) { + Env env = info.Env(); + HandleScope scope(env); + + weak = Weak(Object::New(env)); + weak.SuppressDestruct(); + + persistent = Persistent(Object::New(env)); + persistent.SuppressDestruct(); + + reference = Reference::New(Object::New(env), 2); + reference.SuppressDestruct(); + + Napi::Object configObject = info[0].As(); + + int keyType = + MaybeUnwrap(configObject.Get("keyType")).As().Uint32Value(); + int valType = + MaybeUnwrap(configObject.Get("valType")).As().Uint32Value(); + Napi::Value key = MaybeUnwrap(configObject.Get("key")); + Napi::Value val = MaybeUnwrap(configObject.Get("val")); + + switch (keyType) { + case CPP_STR: + SetObjectWithCppStringKey(weak, key, val, valType); + SetObjectWithCppStringKey(persistent, key, val, valType); + SetObjectWithCppStringKey(reference, key, val, valType); + break; + + case C_STR: + SetObjectWithCStringKey(weak, key, val, valType); + SetObjectWithCStringKey(persistent, key, val, valType); + SetObjectWithCStringKey(reference, key, val, valType); + break; + + case INT: + SetObjectWithIntKey(weak, key, val, valType); + SetObjectWithIntKey(persistent, key, val, valType); + SetObjectWithIntKey(reference, key, val, valType); + + default: + break; + } +} + +void SetCastedObjects(const CallbackInfo& info) { + Env env = info.Env(); + HandleScope scope(env); + + Array ex = Array::New(env); + ex.Set((uint32_t)0, String::New(env, "hello")); + ex.Set(1, String::New(env, "world")); + ex.Set(2, String::New(env, "!")); + + casted_weak = Weak(ex.As()); + casted_weak.SuppressDestruct(); + + casted_persistent = Persistent(ex.As()); + casted_persistent.SuppressDestruct(); + + casted_reference = Reference::New(ex.As(), 2); + casted_reference.SuppressDestruct(); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Value GetFromValue(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0] == String::New(env, "weak")) { + if (weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return weak.Value(); + } + } else if (info[0] == String::New(env, "persistent")) { + return persistent.Value(); + } else { + return reference.Value(); + } +} + +Value GetHelper(ObjectReference& ref, + Object& configObject, + const Napi::Env& env) { + int keyType = + MaybeUnwrap(configObject.Get("keyType")).As().Uint32Value(); + if (ref.IsEmpty()) { + return String::New(env, "No referenced Value"); + } + + switch (keyType) { + case C_STR: { + std::string c_key = + MaybeUnwrap(configObject.Get("key")).As().Utf8Value(); + return MaybeUnwrap(ref.Get(c_key.c_str())); + break; + } + case CPP_STR: { + std::string cpp_key = + MaybeUnwrap(configObject.Get("key")).As().Utf8Value(); + return MaybeUnwrap(ref.Get(cpp_key)); + break; + } + case INT: { + uint32_t key = + MaybeUnwrap(configObject.Get("key")).As().Uint32Value(); + return MaybeUnwrap(ref.Get(key)); + break; + } + + default: + return String::New(env, "Error: Reached end of getter"); + break; + } +} + +Value GetFromGetters(const CallbackInfo& info) { + std::string object_req = info[0].As(); + Object configObject = info[1].As(); + if (object_req == "weak") { + return GetHelper(weak, configObject, info.Env()); + } else if (object_req == "persistent") { + return GetHelper(persistent, configObject, info.Env()); + } + + return GetHelper(reference, configObject, info.Env()); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +// info[1] is the key, and it be either a String or a Number. +Value GetFromGetter(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0] == String::New(env, "weak")) { + if (weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + if (info[1].IsString()) { + return MaybeUnwrap(weak.Get(info[1].As().Utf8Value())); + } else if (info[1].IsNumber()) { + return MaybeUnwrap(weak.Get(info[1].As().Uint32Value())); + } + } + } else if (info[0] == String::New(env, "persistent")) { + if (info[1].IsString()) { + return MaybeUnwrap(persistent.Get(info[1].As().Utf8Value())); + } else if (info[1].IsNumber()) { + return MaybeUnwrap(persistent.Get(info[1].As().Uint32Value())); + } + } else { + if (info[0].IsString()) { + return MaybeUnwrap(reference.Get(info[0].As().Utf8Value())); + } else if (info[0].IsNumber()) { + return MaybeUnwrap(reference.Get(info[0].As().Uint32Value())); + } + } + + return String::New(env, "Error: Reached end of getter"); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Value GetCastedFromValue(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0] == String::New(env, "weak")) { + if (casted_weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return casted_weak.Value(); + } + } else if (info[0] == String::New(env, "persistent")) { + return casted_persistent.Value(); + } else { + return casted_reference.Value(); + } +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +// info[1] is the key and it must be a Number. +Value GetCastedFromGetter(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0] == String::New(env, "weak")) { + if (casted_weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return MaybeUnwrap(casted_weak.Get(info[1].As())); + } + } else if (info[0] == String::New(env, "persistent")) { + return MaybeUnwrap(casted_persistent.Get(info[1].As())); + } else { + return MaybeUnwrap(casted_reference.Get(info[1].As())); + } +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Number UnrefObjects(const CallbackInfo& info) { + Env env = info.Env(); + uint32_t num; + + if (info[0] == String::New(env, "weak")) { + num = weak.Unref(); + } else if (info[0] == String::New(env, "persistent")) { + num = persistent.Unref(); + } else if (info[0] == String::New(env, "references")) { + num = reference.Unref(); + } else if (info[0] == String::New(env, "casted weak")) { + num = casted_weak.Unref(); + } else if (info[0] == String::New(env, "casted persistent")) { + num = casted_persistent.Unref(); + } else { + num = casted_reference.Unref(); + } + + return Number::New(env, num); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Number RefObjects(const CallbackInfo& info) { + Env env = info.Env(); + uint32_t num; + + if (info[0] == String::New(env, "weak")) { + num = weak.Ref(); + } else if (info[0] == String::New(env, "persistent")) { + num = persistent.Ref(); + } else if (info[0] == String::New(env, "references")) { + num = reference.Ref(); + } else if (info[0] == String::New(env, "casted weak")) { + num = casted_weak.Ref(); + } else if (info[0] == String::New(env, "casted persistent")) { + num = casted_persistent.Ref(); + } else { + num = casted_reference.Ref(); + } + + return Number::New(env, num); +} + +Object InitObjectReference(Env env) { + Object exports = Object::New(env); + + exports["setCastedObjects"] = Function::New(env, SetCastedObjects); + exports["setObject"] = Function::New(env, SetObject); + exports["getCastedFromValue"] = Function::New(env, GetCastedFromValue); + exports["getFromGetters"] = Function::New(env, GetFromGetters); + exports["getCastedFromGetter"] = Function::New(env, GetCastedFromGetter); + exports["getFromValue"] = Function::New(env, GetFromValue); + exports["unrefObjects"] = Function::New(env, UnrefObjects); + exports["refObjects"] = Function::New(env, RefObjects); + exports["moveOpTest"] = Function::New(env, MoveOperatorsTest); + exports["setWithTempString"] = Function::New(env, SetWithTempString); + + return exports; +} diff --git a/test/object_reference.js b/test/object_reference.js new file mode 100644 index 000000000..5b713dce8 --- /dev/null +++ b/test/object_reference.js @@ -0,0 +1,264 @@ +/* + * First tests are for setting and getting the ObjectReference on the + * casted Array as Object. Then the tests are for the ObjectReference + * to an empty Object. They test setting the ObjectReference with a C + * string, a JavaScript string, and a JavaScript Number as the keys. + * Then getting the value of those keys through the Reference function + * Value() and through the ObjectReference getters. Finally, they test + * Unref() and Ref() to determine if the reference count is as + * expected and errors are thrown when expected. + */ + +'use strict'; + +const assert = require('assert'); +const testUtil = require('./testUtil'); + +module.exports = require('./common').runTest(test); + +const enumType = { + JS: 0, // Napi::Value + C_STR: 1, // const char * + CPP_STR: 2, // std::string + BOOL: 3, // bool + INT: 4, // uint32_t + DOUBLE: 5, // double + JS_CAST: 6 // napi_value +}; + +const configObjects = [ + { keyType: enumType.C_STR, valType: enumType.JS, key: 'hello', val: 'worlds' }, + { keyType: enumType.C_STR, valType: enumType.C_STR, key: 'hello', val: 'worldd' }, + { keyType: enumType.C_STR, valType: enumType.BOOL, key: 'hello', val: false }, + { keyType: enumType.C_STR, valType: enumType.DOUBLE, key: 'hello', val: 3.56 }, + { keyType: enumType.C_STR, valType: enumType.JS_CAST, key: 'hello_cast', val: 'world' }, + { keyType: enumType.CPP_STR, valType: enumType.JS, key: 'hello_cpp', val: 'world_js' }, + { keyType: enumType.CPP_STR, valType: enumType.JS_CAST, key: 'hello_cpp', val: 'world_js_cast' }, + { keyType: enumType.CPP_STR, valType: enumType.CPP_STR, key: 'hello_cpp', val: 'world_cpp_str' }, + { keyType: enumType.CPP_STR, valType: enumType.BOOL, key: 'hello_cpp', val: true }, + { keyType: enumType.CPP_STR, valType: enumType.DOUBLE, key: 'hello_cpp', val: 3.58 }, + { keyType: enumType.INT, valType: enumType.JS, key: 1, val: 'hello world' }, + { keyType: enumType.INT, valType: enumType.JS_CAST, key: 2, val: 'hello world' }, + { keyType: enumType.INT, valType: enumType.C_STR, key: 3, val: 'hello world' }, + { keyType: enumType.INT, valType: enumType.CPP_STR, key: 8, val: 'hello world' }, + { keyType: enumType.INT, valType: enumType.BOOL, key: 3, val: false }, + { keyType: enumType.INT, valType: enumType.DOUBLE, key: 4, val: 3.14159 } +]; + +function test (binding) { + binding.objectreference.moveOpTest(); + binding.objectreference.setWithTempString('testValue'); + + function testCastedEqual (testToCompare) { + const compareTest = ['hello', 'world', '!']; + if (testToCompare instanceof Array) { + assert.deepEqual(compareTest, testToCompare); + } else if (testToCompare instanceof String) { + assert.deepEqual('No Referenced Value', testToCompare); + } else { + assert.fail(); + } + } + + return testUtil.runGCTests([ + 'Weak Casted Array', + () => { + binding.objectreference.setCastedObjects(); + const test = binding.objectreference.getCastedFromValue('weak'); + const test2 = []; + test2[0] = binding.objectreference.getCastedFromGetter('weak', 0); + test2[1] = binding.objectreference.getCastedFromGetter('weak', 1); + test2[2] = binding.objectreference.getCastedFromGetter('weak', 2); + + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'Persistent Casted Array', + () => { + binding.objectreference.setCastedObjects(); + const test = binding.objectreference.getCastedFromValue('persistent'); + const test2 = []; + test2[0] = binding.objectreference.getCastedFromGetter('persistent', 0); + test2[1] = binding.objectreference.getCastedFromGetter('persistent', 1); + test2[2] = binding.objectreference.getCastedFromGetter('persistent', 2); + + assert.ok(test instanceof Array); + assert.ok(test2 instanceof Array); + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'References Casted Array', + () => { + binding.objectreference.setCastedObjects(); + const test = binding.objectreference.getCastedFromValue(); + const test2 = []; + test2[0] = binding.objectreference.getCastedFromGetter('reference', 0); + test2[1] = binding.objectreference.getCastedFromGetter('reference', 1); + test2[2] = binding.objectreference.getCastedFromGetter('reference', 2); + + assert.ok(test instanceof Array); + assert.ok(test2 instanceof Array); + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'Weak', + () => { + for (const configObject of configObjects) { + binding.objectreference.setObject(configObject); + const test = binding.objectreference.getFromValue('weak'); + const test2 = binding.objectreference.getFromGetters('weak', configObject); + + const assertObject = { + [configObject.key]: configObject.val + }; + assert.deepEqual(assertObject, test); + assert.equal(configObject.val, test2); + } + }, () => { + const configObjA = { keyType: enumType.INT, valType: enumType.JS, key: 0, val: 'hello' }; + const configObjB = { keyType: enumType.INT, valType: enumType.JS, key: 1, val: 'world' }; + binding.objectreference.setObject(configObjA); + binding.objectreference.setObject(configObjB); + + const test = binding.objectreference.getFromValue('weak'); + const test2 = binding.objectreference.getFromGetters('weak', configObjA); + const test3 = binding.objectreference.getFromGetters('weak', configObjB); + assert.deepEqual({ 1: 'world' }, test); + assert.equal(undefined, test2); + assert.equal('world', test3); + }, + () => { + binding.objectreference.setObject({ keyType: enumType.JS, valType: enumType.JS, key: 'hello', val: 'world' }); + assert.doesNotThrow( + () => { + let rcount = binding.objectreference.refObjects('weak'); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects('weak'); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects('weak'); + }, + Error + ); + }, + + 'Persistent', + () => { + for (const configObject of configObjects) { + binding.objectreference.setObject(configObject); + const test = binding.objectreference.getFromValue('persistent'); + const test2 = binding.objectreference.getFromGetters('persistent', configObject); + const assertObject = { + [configObject.key]: configObject.val + }; + + assert.deepEqual(assertObject, test); + assert.equal(configObject.val, test2); + } + }, + () => { + binding.objectreference.setObject({ keyType: enumType.CPP_STR, valType: enumType.JS, key: 'hello', val: 'world' }); + const test = binding.objectreference.getFromValue('persistent'); + const test2 = binding.objectreference.getFromValue('persistent', 'hello'); + + assert.deepEqual({ hello: 'world' }, test); + assert.deepEqual({ hello: 'world' }, test2); + assert.deepEqual(test, test2); + }, + () => { + const configObjA = { keyType: enumType.INT, valType: enumType.JS, key: 0, val: 'hello' }; + const configObjB = { keyType: enumType.INT, valType: enumType.JS, key: 1, val: 'world' }; + binding.objectreference.setObject(configObjA); + binding.objectreference.setObject(configObjB); + + const test = binding.objectreference.getFromValue('persistent'); + const test2 = binding.objectreference.getFromGetters('persistent', configObjA); + const test3 = binding.objectreference.getFromGetters('persistent', configObjB); + + assert.deepEqual({ 1: 'world' }, test); + assert.equal(undefined, test2); + assert.equal('world', test3); + }, + () => { + binding.objectreference.setObject({ keyType: enumType.CPP_STR, valType: enumType.JS, key: 'hello', val: 'world' }); + assert.doesNotThrow( + () => { + let rcount = binding.objectreference.unrefObjects('persistent'); + assert.equal(rcount, 0); + rcount = binding.objectreference.refObjects('persistent'); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects('persistent'); + assert.equal(rcount, 0); + rcount = binding.objectreference.refObjects('persistent'); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects('persistent'); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects('persistent'); + }, + Error + ); + }, + + 'References', + () => { + for (const configObject of configObjects) { + binding.objectreference.setObject(configObject); + const test = binding.objectreference.getFromValue(); + const test2 = binding.objectreference.getFromGetters('reference', configObject); + const assertObject = { + [configObject.key]: configObject.val + }; + assert.deepEqual(assertObject, test); + assert.equal(configObject.val, test2); + } + }, + () => { + const configObjA = { keyType: enumType.INT, valType: enumType.JS, key: 0, val: 'hello' }; + const configObjB = { keyType: enumType.INT, valType: enumType.JS, key: 1, val: 'world' }; + binding.objectreference.setObject(configObjA); + binding.objectreference.setObject(configObjB); + const test = binding.objectreference.getFromValue(); + + const test2 = binding.objectreference.getFromGetters('reference', configObjA); + const test3 = binding.objectreference.getFromGetters('reference', configObjB); + + assert.deepEqual({ 1: 'world' }, test); + assert.equal(undefined, test2); + assert.equal('world', test3); + }, + () => { + binding.objectreference.setObject({ keyType: enumType.CPP_STR, valType: enumType.JS, key: 'hello', val: 'world' }); + assert.doesNotThrow( + () => { + let rcount = binding.objectreference.unrefObjects('references'); + assert.equal(rcount, 1); + rcount = binding.objectreference.refObjects('references'); + assert.equal(rcount, 2); + rcount = binding.objectreference.unrefObjects('references'); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects('references'); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects('references'); + }, + Error + ); + } + ]); +} diff --git a/test/objectreference.cc b/test/objectreference.cc deleted file mode 100644 index 3143216dc..000000000 --- a/test/objectreference.cc +++ /dev/null @@ -1,218 +0,0 @@ -/* ObjectReference can be used to create references to Values that -are not Objects by creating a blank Object and setting Values to -it. Subclasses of Objects can only be set using an ObjectReference -by first casting it as an Object. */ - -#include "napi.h" - -using namespace Napi; - -ObjectReference weak; -ObjectReference persistent; -ObjectReference reference; - -ObjectReference casted_weak; -ObjectReference casted_persistent; -ObjectReference casted_reference; - -// info[0] is the key, which can be either a string or a number. -// info[1] is the value. -// info[2] is a flag that differentiates whether the key is a -// C string or a JavaScript string. -void SetObjects(const CallbackInfo& info) { - Env env = info.Env(); - HandleScope scope(env); - - weak = Weak(Object::New(env)); - weak.SuppressDestruct(); - - persistent = Persistent(Object::New(env)); - persistent.SuppressDestruct(); - - reference = Reference::New(Object::New(env), 2); - reference.SuppressDestruct(); - - if (info[0].IsString()) { - if (info[2].As() == String::New(env, "javascript")) { - weak.Set(info[0].As(), info[1]); - persistent.Set(info[0].As(), info[1]); - reference.Set(info[0].As(), info[1]); - } else { - weak.Set(info[0].As().Utf8Value(), info[1]); - persistent.Set(info[0].As().Utf8Value(), info[1]); - reference.Set(info[0].As().Utf8Value(), info[1]); - } - } else if (info[0].IsNumber()) { - weak.Set(info[0].As(), info[1]); - persistent.Set(info[0].As(), info[1]); - reference.Set(info[0].As(), info[1]); - } -} - -void SetCastedObjects(const CallbackInfo& info) { - Env env = info.Env(); - HandleScope scope(env); - - Array ex = Array::New(env); - ex.Set((uint32_t)0, String::New(env, "hello")); - ex.Set(1, String::New(env, "world")); - ex.Set(2, String::New(env, "!")); - - casted_weak = Weak(ex.As()); - casted_weak.SuppressDestruct(); - - casted_persistent = Persistent(ex.As()); - casted_persistent.SuppressDestruct(); - - casted_reference = Reference::New(ex.As(), 2); - casted_reference.SuppressDestruct(); -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -Value GetFromValue(const CallbackInfo& info) { - Env env = info.Env(); - - if (info[0].As() == String::New(env, "weak")) { - if (weak.IsEmpty()) { - return String::New(env, "No Referenced Value"); - } else { - return weak.Value(); - } - } else if (info[0].As() == String::New(env, "persistent")) { - return persistent.Value(); - } else { - return reference.Value(); - } -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -// info[1] is the key, and it be either a String or a Number. -Value GetFromGetter(const CallbackInfo& info) { - Env env = info.Env(); - - if (info[0].As() == String::New(env, "weak")) { - if (weak.IsEmpty()) { - return String::New(env, "No Referenced Value"); - } else { - if (info[1].IsString()) { - return weak.Get(info[1].As().Utf8Value()); - } else if (info[1].IsNumber()) { - return weak.Get(info[1].As().Uint32Value()); - } - } - } else if (info[0].As() == String::New(env, "persistent")) { - if (info[1].IsString()) { - return persistent.Get(info[1].As().Utf8Value()); - } else if (info[1].IsNumber()) { - return persistent.Get(info[1].As().Uint32Value()); - } - } else { - if (info[0].IsString()) { - return reference.Get(info[0].As().Utf8Value()); - } else if (info[0].IsNumber()) { - return reference.Get(info[0].As().Uint32Value()); - } - } - - return String::New(env, "Error: Reached end of getter"); -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -Value GetCastedFromValue(const CallbackInfo& info) { - Env env = info.Env(); - - if (info[0].As() == String::New(env, "weak")) { - if (casted_weak.IsEmpty()) { - return String::New(env, "No Referenced Value"); - } else { - return casted_weak.Value(); - } - } else if (info[0].As() == String::New(env, "persistent")) { - return casted_persistent.Value(); - } else { - return casted_reference.Value(); - } -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -// info[1] is the key and it must be a Number. -Value GetCastedFromGetter(const CallbackInfo& info) { - Env env = info.Env(); - - if (info[0].As() == String::New(env, "weak")) { - if (casted_weak.IsEmpty()) { - return String::New(env, "No Referenced Value"); - } else { - return casted_weak.Get(info[1].As()); - } - } else if (info[0].As() == String::New(env, "persistent")) { - return casted_persistent.Get(info[1].As()); - } else { - return casted_reference.Get(info[1].As()); - } -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -Number UnrefObjects(const CallbackInfo& info) { - Env env = info.Env(); - uint32_t num; - - if (info[0].As() == String::New(env, "weak")) { - num = weak.Unref(); - } else if (info[0].As() == String::New(env, "persistent")) { - num = persistent.Unref(); - } else if (info[0].As() == String::New(env, "references")) { - num = reference.Unref(); - } else if (info[0].As() == String::New(env, "casted weak")) { - num = casted_weak.Unref(); - } else if (info[0].As() == String::New(env, "casted persistent")) { - num = casted_persistent.Unref(); - } else { - num = casted_reference.Unref(); - } - - return Number::New(env, num); -} - -// info[0] is a flag to determine if the weak, persistent, or -// multiple reference ObjectReference is being requested. -Number RefObjects(const CallbackInfo& info) { - Env env = info.Env(); - uint32_t num; - - if (info[0].As() == String::New(env, "weak")) { - num = weak.Ref(); - } else if (info[0].As() == String::New(env, "persistent")) { - num = persistent.Ref(); - } else if (info[0].As() == String::New(env, "references")) { - num = reference.Ref(); - } else if (info[0].As() == String::New(env, "casted weak")) { - num = casted_weak.Ref(); - } else if (info[0].As() == String::New(env, "casted persistent")) { - num = casted_persistent.Ref(); - } else { - num = casted_reference.Ref(); - } - - return Number::New(env, num); -} - -Object InitObjectReference(Env env) { - Object exports = Object::New(env); - - exports["setCastedObjects"] = Function::New(env, SetCastedObjects); - exports["setObjects"] = Function::New(env, SetObjects); - exports["getCastedFromValue"] = Function::New(env, GetCastedFromValue); - exports["getFromGetter"] = Function::New(env, GetFromGetter); - exports["getCastedFromGetter"] = Function::New(env, GetCastedFromGetter); - exports["getFromValue"] = Function::New(env, GetFromValue); - exports["unrefObjects"] = Function::New(env, UnrefObjects); - exports["refObjects"] = Function::New(env, RefObjects); - - return exports; -} diff --git a/test/objectreference.js b/test/objectreference.js deleted file mode 100644 index 55b95dba6..000000000 --- a/test/objectreference.js +++ /dev/null @@ -1,260 +0,0 @@ -/* - * First tests are for setting and getting the ObjectReference on the - * casted Array as Object. Then the tests are for the ObjectReference - * to an empty Object. They test setting the ObjectReference with a C - * string, a JavaScript string, and a JavaScript Number as the keys. - * Then getting the value of those keys through the Reference function - * Value() and through the ObjectReference getters. Finally, they test - * Unref() and Ref() to determine if the reference count is as - * expected and errors are thrown when expected. - */ - -'use strict'; -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const testUtil = require('./testUtil'); - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); - -function test(binding) { - function testCastedEqual(testToCompare) { - var compare_test = ["hello", "world", "!"]; - if (testToCompare instanceof Array) { - assert.deepEqual(compare_test, testToCompare); - } else if (testToCompare instanceof String) { - assert.deepEqual("No Referenced Value", testToCompare); - } else { - assert.fail(); - } - } - - return testUtil.runGCTests([ - 'Weak Casted Array', - () => { - binding.objectreference.setCastedObjects(); - var test = binding.objectreference.getCastedFromValue("weak"); - var test2 = new Array(); - test2[0] = binding.objectreference.getCastedFromGetter("weak", 0); - test2[1] = binding.objectreference.getCastedFromGetter("weak", 1); - test2[2] = binding.objectreference.getCastedFromGetter("weak", 2); - - testCastedEqual(test); - testCastedEqual(test2); - }, - - 'Persistent Casted Array', - () => { - binding.objectreference.setCastedObjects(); - const test = binding.objectreference.getCastedFromValue("persistent"); - const test2 = new Array(); - test2[0] = binding.objectreference.getCastedFromGetter("persistent", 0); - test2[1] = binding.objectreference.getCastedFromGetter("persistent", 1); - test2[2] = binding.objectreference.getCastedFromGetter("persistent", 2); - - assert.ok(test instanceof Array); - assert.ok(test2 instanceof Array); - testCastedEqual(test); - testCastedEqual(test2); - }, - - 'References Casted Array', - () => { - binding.objectreference.setCastedObjects(); - const test = binding.objectreference.getCastedFromValue(); - const test2 = new Array(); - test2[0] = binding.objectreference.getCastedFromGetter("reference", 0); - test2[1] = binding.objectreference.getCastedFromGetter("reference", 1); - test2[2] = binding.objectreference.getCastedFromGetter("reference", 2); - - assert.ok(test instanceof Array); - assert.ok(test2 instanceof Array); - testCastedEqual(test); - testCastedEqual(test2); - }, - - 'Weak', - () => { - binding.objectreference.setObjects("hello", "world"); - const test = binding.objectreference.getFromValue("weak"); - const test2 = binding.objectreference.getFromGetter("weak", "hello"); - - assert.deepEqual({ hello: "world"}, test); - assert.equal("world", test2); - assert.equal(test["hello"], test2); - }, - () => { - binding.objectreference.setObjects("hello", "world", "javascript"); - const test = binding.objectreference.getFromValue("weak"); - const test2 = binding.objectreference.getFromValue("weak", "hello"); - - assert.deepEqual({ hello: "world" }, test); - assert.deepEqual({ hello: "world" }, test2); - assert.equal(test, test2); - }, - () => { - binding.objectreference.setObjects(1, "hello world"); - const test = binding.objectreference.getFromValue("weak"); - const test2 = binding.objectreference.getFromGetter("weak", 1); - - assert.deepEqual({ 1: "hello world" }, test); - assert.equal("hello world", test2); - assert.equal(test[1], test2); - }, - () => { - binding.objectreference.setObjects(0, "hello"); - binding.objectreference.setObjects(1, "world"); - const test = binding.objectreference.getFromValue("weak"); - const test2 = binding.objectreference.getFromGetter("weak", 0); - const test3 = binding.objectreference.getFromGetter("weak", 1); - - assert.deepEqual({ 1: "world" }, test); - assert.equal(undefined, test2); - assert.equal("world", test3); - }, - () => { - binding.objectreference.setObjects("hello", "world"); - assert.doesNotThrow( - () => { - var rcount = binding.objectreference.refObjects("weak"); - assert.equal(rcount, 1); - rcount = binding.objectreference.unrefObjects("weak"); - assert.equal(rcount, 0); - }, - Error - ); - assert.throws( - () => { - binding.objectreference.unrefObjects("weak"); - }, - Error - ); - }, - - 'Persistent', - () => { - binding.objectreference.setObjects("hello", "world"); - const test = binding.objectreference.getFromValue("persistent"); - const test2 = binding.objectreference.getFromGetter("persistent", "hello"); - - assert.deepEqual({ hello: "world" }, test); - assert.equal("world", test2); - assert.equal(test["hello"], test2); - }, - () => { - binding.objectreference.setObjects("hello", "world", "javascript"); - const test = binding.objectreference.getFromValue("persistent"); - const test2 = binding.objectreference.getFromValue("persistent", "hello"); - - assert.deepEqual({ hello: "world" }, test); - assert.deepEqual({ hello: "world" }, test2); - assert.deepEqual(test, test2); - }, - () => { - binding.objectreference.setObjects(1, "hello world"); - const test = binding.objectreference.getFromValue("persistent"); - const test2 = binding.objectreference.getFromGetter("persistent", 1); - - assert.deepEqual({ 1: "hello world"}, test); - assert.equal("hello world", test2); - assert.equal(test[1], test2); - }, - () => { - binding.objectreference.setObjects(0, "hello"); - binding.objectreference.setObjects(1, "world"); - const test = binding.objectreference.getFromValue("persistent"); - const test2 = binding.objectreference.getFromGetter("persistent", 0); - const test3 = binding.objectreference.getFromGetter("persistent", 1); - - assert.deepEqual({ 1: "world"}, test); - assert.equal(undefined, test2); - assert.equal("world", test3); - }, - () => { - binding.objectreference.setObjects("hello", "world"); - assert.doesNotThrow( - () => { - var rcount = binding.objectreference.unrefObjects("persistent"); - assert.equal(rcount, 0); - rcount = binding.objectreference.refObjects("persistent"); - assert.equal(rcount, 1); - rcount = binding.objectreference.unrefObjects("persistent"); - assert.equal(rcount, 0); - rcount = binding.objectreference.refObjects("persistent"); - assert.equal(rcount, 1); - rcount = binding.objectreference.unrefObjects("persistent"); - assert.equal(rcount, 0); - }, - Error - ); - assert.throws( - () => { - binding.objectreference.unrefObjects("persistent"); - }, - Error - ); - }, - - 'References', - () => { - binding.objectreference.setObjects("hello", "world"); - const test = binding.objectreference.getFromValue(); - const test2 = binding.objectreference.getFromGetter("hello"); - - assert.deepEqual({ hello: "world" }, test); - assert.equal("world", test2); - assert.equal(test["hello"], test2); - }, - () => { - binding.objectreference.setObjects("hello", "world", "javascript"); - const test = binding.objectreference.getFromValue(); - const test2 = binding.objectreference.getFromValue("hello"); - - assert.deepEqual({ hello: "world" }, test); - assert.deepEqual({ hello: "world" }, test2); - assert.deepEqual(test, test2); - }, - () => { - binding.objectreference.setObjects(1, "hello world"); - const test = binding.objectreference.getFromValue(); - const test2 = binding.objectreference.getFromGetter(1); - - assert.deepEqual({ 1: "hello world"}, test); - assert.equal("hello world", test2); - assert.equal(test[1], test2); - }, - () => { - binding.objectreference.setObjects(0, "hello"); - binding.objectreference.setObjects(1, "world"); - const test = binding.objectreference.getFromValue(); - const test2 = binding.objectreference.getFromGetter(0); - const test3 = binding.objectreference.getFromGetter(1); - - assert.deepEqual({ 1: "world"}, test); - assert.equal(undefined, test2); - assert.equal("world", test3); - }, - () => { - binding.objectreference.setObjects("hello", "world"); - assert.doesNotThrow( - () => { - var rcount = binding.objectreference.unrefObjects("references"); - assert.equal(rcount, 1); - rcount = binding.objectreference.refObjects("references"); - assert.equal(rcount, 2); - rcount = binding.objectreference.unrefObjects("references"); - assert.equal(rcount, 1); - rcount = binding.objectreference.unrefObjects("references"); - assert.equal(rcount, 0); - }, - Error - ); - assert.throws( - () => { - binding.objectreference.unrefObjects("references"); - }, - Error - ); - } - ]) -}; diff --git a/test/objectwrap-removewrap.js b/test/objectwrap-removewrap.js deleted file mode 100644 index 560f61d46..000000000 --- a/test/objectwrap-removewrap.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -if (process.argv[2] === 'child') { - // Create a single wrapped instance then exit. - return new (require(process.argv[3]).objectwrap.Test)(); -} - -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const testUtil = require('./testUtil'); - -function test(bindingName) { - return testUtil.runGCTests([ - 'objectwrap removewrap test', - () => { - const binding = require(bindingName); - const Test = binding.objectwrap_removewrap.Test; - const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; - - assert.strictEqual(getDtorCalled(), 0); - assert.throws(() => { - new Test(); - }); - assert.strictEqual(getDtorCalled(), 1); - }, - // Test that gc does not crash. - () => {} - ]); - - // Start a child process that creates a single wrapped instance to ensure that - // it is properly freed at its exit. It must not segfault. - // Re: https://github.com/nodejs/node-addon-api/issues/660 - const child = spawnSync(process.execPath, [ - __filename, 'child', bindingName - ]); - assert.strictEqual(child.signal, null); - assert.strictEqual(child.status, 0); -} - -module.exports = test(`./build/${buildType}/binding.node`) - .then(() => test(`./build/${buildType}/binding_noexcept.node`)); diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 2ffc85a25..1ee0c9ad7 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -1,31 +1,35 @@ #include +#include "test_helper.h" Napi::ObjectReference testStaticContextRef; Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { - return testStaticContextRef.Value().Get("value"); + return MaybeUnwrap(testStaticContextRef.Value().Get("value")); } -void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { +void StaticSetter(const Napi::CallbackInfo& /*info*/, + const Napi::Value& value) { testStaticContextRef.Value().Set("value", value); } +void StaticMethodVoidCb(const Napi::CallbackInfo& info) { + StaticSetter(info, info[0].As()); +} + Napi::Value TestStaticMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static"); } Napi::Value TestStaticMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static internal"); } class Test : public Napi::ObjectWrap { -public: - Test(const Napi::CallbackInfo& info) : - Napi::ObjectWrap(info) { - - if(info.Length() > 0) { + public: + Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + if (info.Length() > 0) { finalizeCb_ = Napi::Persistent(info[0].As()); } // Create an own instance property. @@ -34,28 +38,36 @@ class Test : public Napi::ObjectWrap { info.This().As(), "ownProperty", OwnPropertyGetter, - napi_enumerable, this)); + napi_enumerable, + this)); // Create an own instance property with a templated function. info.This().As().DefineProperty( - Napi::PropertyDescriptor::Accessor("ownPropertyT", - napi_enumerable, this)); + Napi::PropertyDescriptor::Accessor( + "ownPropertyT", napi_enumerable, this)); bufref_ = Napi::Persistent(Napi::Buffer::New( Env(), static_cast(malloc(1)), 1, - [](Napi::Env, uint8_t* bufaddr) { - free(bufaddr); - })); + [](Napi::Env, uint8_t* bufaddr) { free(bufaddr); })); } static Napi::Value OwnPropertyGetter(const Napi::CallbackInfo& info) { return static_cast(info.Data())->Getter(info); } + static Napi::Value CanUnWrap(const Napi::CallbackInfo& info) { + Napi::Object wrappedObject = info[0].As(); + std::string expectedString = info[1].As(); + Test* nativeObject = Test::Unwrap(wrappedObject); + std::string strVal = MaybeUnwrap(nativeObject->Getter(info).ToString()); + + return Napi::Boolean::New(info.Env(), strVal == expectedString); + } + void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { - value_ = value.ToString(); + value_ = MaybeUnwrap(value.ToString()); } Napi::Value Getter(const Napi::CallbackInfo& info) { @@ -63,12 +75,12 @@ class Test : public Napi::ObjectWrap { } Napi::Value TestMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance"); } Napi::Value TestMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance internal"); } @@ -80,113 +92,189 @@ class Test : public Napi::ObjectWrap { Napi::Value Iterator(const Napi::CallbackInfo& info) { Napi::Array array = Napi::Array::New(info.Env()); array.Set(array.Length(), Napi::String::From(info.Env(), value_)); - return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); + return MaybeUnwrap( + MaybeUnwrap(array.Get(MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), "iterator")))) + .As() + .Call(array, {})); } - void TestVoidMethodT(const Napi::CallbackInfo &info) { - value_ = info[0].ToString(); + void TestVoidMethodT(const Napi::CallbackInfo& info) { + value_ = MaybeUnwrap(info[0].ToString()); } - Napi::Value TestMethodT(const Napi::CallbackInfo &info) { - return Napi::String::New(info.Env(), value_); + Napi::Value TestMethodT(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), value_); } static Napi::Value TestStaticMethodT(const Napi::CallbackInfo& info) { - return Napi::String::New(info.Env(), s_staticMethodText); + return Napi::String::New(info.Env(), s_staticMethodText); } static void TestStaticVoidMethodT(const Napi::CallbackInfo& info) { - s_staticMethodText = info[0].ToString(); + s_staticMethodText = MaybeUnwrap(info[0].ToString()); } static void Initialize(Napi::Env env, Napi::Object exports) { - - Napi::Symbol kTestStaticValueInternal = Napi::Symbol::New(env, "kTestStaticValueInternal"); - Napi::Symbol kTestStaticAccessorInternal = Napi::Symbol::New(env, "kTestStaticAccessorInternal"); - Napi::Symbol kTestStaticAccessorTInternal = Napi::Symbol::New(env, "kTestStaticAccessorTInternal"); - Napi::Symbol kTestStaticMethodInternal = Napi::Symbol::New(env, "kTestStaticMethodInternal"); - Napi::Symbol kTestStaticMethodTInternal = Napi::Symbol::New(env, "kTestStaticMethodTInternal"); - Napi::Symbol kTestStaticVoidMethodTInternal = Napi::Symbol::New(env, "kTestStaticVoidMethodTInternal"); - - Napi::Symbol kTestValueInternal = Napi::Symbol::New(env, "kTestValueInternal"); - Napi::Symbol kTestAccessorInternal = Napi::Symbol::New(env, "kTestAccessorInternal"); - Napi::Symbol kTestAccessorTInternal = Napi::Symbol::New(env, "kTestAccessorTInternal"); - Napi::Symbol kTestMethodInternal = Napi::Symbol::New(env, "kTestMethodInternal"); - Napi::Symbol kTestMethodTInternal = Napi::Symbol::New(env, "kTestMethodTInternal"); - Napi::Symbol kTestVoidMethodTInternal = Napi::Symbol::New(env, "kTestVoidMethodTInternal"); - - exports.Set("Test", DefineClass(env, "Test", { - - // expose symbols for testing - StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), - StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), - StaticValue("kTestStaticAccessorTInternal", kTestStaticAccessorTInternal), - StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), - StaticValue("kTestStaticMethodTInternal", kTestStaticMethodTInternal), - StaticValue("kTestStaticVoidMethodTInternal", kTestStaticVoidMethodTInternal), - StaticValue("kTestValueInternal", kTestValueInternal), - StaticValue("kTestAccessorInternal", kTestAccessorInternal), - StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), - StaticValue("kTestMethodInternal", kTestMethodInternal), - StaticValue("kTestMethodTInternal", kTestMethodTInternal), - StaticValue("kTestVoidMethodTInternal", kTestVoidMethodTInternal), - - // test data - StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), - StaticValue(kTestStaticValueInternal, Napi::Number::New(env, 5), napi_default), - - StaticAccessor("testStaticGetter", &StaticGetter, nullptr, napi_enumerable), - StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), - StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor<&StaticGetter>("testStaticGetterT"), - StaticAccessor<&StaticGetter, &StaticSetter>("testStaticGetSetT"), - StaticAccessor<&StaticGetter, &StaticSetter>(kTestStaticAccessorTInternal), - - StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), - StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), - StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), - StaticMethod<&TestStaticMethodT>("testStaticMethodT"), - StaticMethod<&TestStaticVoidMethodT>(kTestStaticVoidMethodTInternal), - StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), - - InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), - InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), - - InstanceAccessor("testGetter", &Test::Getter, nullptr, napi_enumerable), - InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), - InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor<&Test::Getter>("testGetterT"), - InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), - InstanceAccessor<&Test::Getter, &Test::Setter>(kTestAccessorInternal), - - InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), - InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), - InstanceMethod<&Test::TestMethodT>("testMethodT"), - InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), - InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), - InstanceMethod<&Test::TestVoidMethodT>(kTestVoidMethodTInternal), - - // conventions - InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), - InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iterator, napi_default), - - })); + Napi::Symbol kTestStaticValueInternal = + Napi::Symbol::New(env, "kTestStaticValueInternal"); + Napi::Symbol kTestStaticAccessorInternal = + Napi::Symbol::New(env, "kTestStaticAccessorInternal"); + Napi::Symbol kTestStaticAccessorTInternal = + Napi::Symbol::New(env, "kTestStaticAccessorTInternal"); + Napi::Symbol kTestStaticMethodInternal = + Napi::Symbol::New(env, "kTestStaticMethodInternal"); + Napi::Symbol kTestStaticMethodTInternal = + Napi::Symbol::New(env, "kTestStaticMethodTInternal"); + Napi::Symbol kTestStaticVoidMethodTInternal = + Napi::Symbol::New(env, "kTestStaticVoidMethodTInternal"); + Napi::Symbol kTestStaticVoidMethodInternal = + Napi::Symbol::New(env, "kTestStaticVoidMethodInternal"); + Napi::Symbol kTestValueInternal = + Napi::Symbol::New(env, "kTestValueInternal"); + Napi::Symbol kTestAccessorInternal = + Napi::Symbol::New(env, "kTestAccessorInternal"); + Napi::Symbol kTestAccessorTInternal = + Napi::Symbol::New(env, "kTestAccessorTInternal"); + Napi::Symbol kTestMethodInternal = + Napi::Symbol::New(env, "kTestMethodInternal"); + Napi::Symbol kTestMethodTInternal = + Napi::Symbol::New(env, "kTestMethodTInternal"); + Napi::Symbol kTestVoidMethodTInternal = + Napi::Symbol::New(env, "kTestVoidMethodTInternal"); + + exports.Set( + "Test", + DefineClass( + env, + "Test", + { + + // expose symbols for testing + StaticValue("kTestStaticValueInternal", + kTestStaticValueInternal), + StaticValue("kTestStaticAccessorInternal", + kTestStaticAccessorInternal), + StaticValue("kTestStaticAccessorTInternal", + kTestStaticAccessorTInternal), + StaticValue("kTestStaticMethodInternal", + kTestStaticMethodInternal), + StaticValue("kTestStaticMethodTInternal", + kTestStaticMethodTInternal), + StaticValue("kTestStaticVoidMethodInternal", + kTestStaticVoidMethodInternal), + StaticValue("kTestStaticVoidMethodTInternal", + kTestStaticVoidMethodTInternal), + StaticValue("kTestValueInternal", kTestValueInternal), + StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), + StaticValue("kTestMethodInternal", kTestMethodInternal), + StaticValue("kTestMethodTInternal", kTestMethodTInternal), + StaticValue("kTestVoidMethodTInternal", + kTestVoidMethodTInternal), + + // test data + StaticValue("testStaticValue", + Napi::String::New(env, "value"), + napi_enumerable), + StaticValue(kTestStaticValueInternal, + Napi::Number::New(env, 5), + napi_default), + + StaticAccessor("testStaticGetter", + &StaticGetter, + nullptr, + napi_enumerable), + StaticAccessor( + "testStaticSetter", nullptr, &StaticSetter, napi_default), + StaticAccessor("testStaticGetSet", + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor(kTestStaticAccessorInternal, + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor<&StaticGetter>("testStaticGetterT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + "testStaticGetSetT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + kTestStaticAccessorTInternal), + StaticMethod( + "testStaticVoidMethod", &StaticMethodVoidCb, napi_default), + StaticMethod(kTestStaticVoidMethodInternal, + &StaticMethodVoidCb, + napi_default), + StaticMethod( + "testStaticMethod", &TestStaticMethod, napi_enumerable), + StaticMethod(kTestStaticMethodInternal, + &TestStaticMethodInternal, + napi_default), + StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), + StaticMethod<&TestStaticMethodT>("testStaticMethodT"), + StaticMethod<&TestStaticVoidMethodT>( + kTestStaticVoidMethodTInternal), + StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), + StaticMethod("canUnWrap", &CanUnWrap, napi_enumerable), + InstanceValue("testValue", + Napi::Boolean::New(env, true), + napi_enumerable), + InstanceValue(kTestValueInternal, + Napi::Boolean::New(env, false), + napi_enumerable), + + InstanceAccessor( + "testGetter", &Test::Getter, nullptr, napi_enumerable), + InstanceAccessor( + "testSetter", nullptr, &Test::Setter, napi_default), + InstanceAccessor("testGetSet", + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor(kTestAccessorInternal, + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor<&Test::Getter>("testGetterT"), + InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), + InstanceAccessor<&Test::Getter, &Test::Setter>( + kTestAccessorInternal), + + InstanceMethod( + "testMethod", &Test::TestMethod, napi_enumerable), + InstanceMethod(kTestMethodInternal, + &Test::TestMethodInternal, + napi_default), + InstanceMethod<&Test::TestMethodT>("testMethodT"), + InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), + InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), + InstanceMethod<&Test::TestVoidMethodT>( + kTestVoidMethodTInternal), + + // conventions + InstanceAccessor( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "toStringTag")), + &Test::ToStringTag, + nullptr, + napi_enumerable), + InstanceMethod( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "iterator")), + &Test::Iterator, + napi_default), + + })); } void Finalize(Napi::Env env) { - - if(finalizeCb_.IsEmpty()) { + if (finalizeCb_.IsEmpty()) { return; } finalizeCb_.Call(env.Global(), {Napi::Boolean::New(env, true)}); finalizeCb_.Unref(); - } -private: + private: std::string value_; Napi::FunctionReference finalizeCb_; diff --git a/test/objectwrap.js b/test/objectwrap.js index 3a4168359..a0d278062 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -1,9 +1,12 @@ +/* eslint-disable no-lone-blocks */ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -async function test(binding) { +module.exports = require('./common').runTest(test); + +async function test (binding) { const Test = binding.objectwrap.Test; const testValue = (obj, clazz) => { @@ -21,11 +24,15 @@ async function test(binding) { obj.testSetter = 'instance getter 2'; assert.strictEqual(obj.testGetter, 'instance getter 2'); assert.strictEqual(obj.testGetterT, 'instance getter 2'); + + assert.throws(() => clazz.prototype.testGetter, /Invalid argument/); + assert.throws(() => clazz.prototype.testGetterT, /Invalid argument/); } // read write-only { let error; + // eslint-disable-next-line no-unused-vars try { const read = obj.testSetter; } catch (e) { error = e; } // no error assert.strictEqual(error, undefined); @@ -57,6 +64,9 @@ async function test(binding) { obj.testGetSetT = 'instance getset 4'; assert.strictEqual(obj.testGetSetT, 'instance getset 4'); + + assert.throws(() => { clazz.prototype.testGetSet = 'instance getset'; }, /Invalid argument/); + assert.throws(() => { clazz.prototype.testGetSetT = 'instance getset'; }, /Invalid argument/); } // rw symbol @@ -94,6 +104,9 @@ async function test(binding) { assert.strictEqual(obj.testMethodT(), 'method<>(const char*)'); obj[clazz.kTestVoidMethodTInternal]('method<>(Symbol)'); assert.strictEqual(obj[clazz.kTestMethodTInternal](), 'method<>(Symbol)'); + assert.throws(() => clazz.prototype.testMethod('method')); + assert.throws(() => clazz.prototype.testMethodT()); + assert.throws(() => clazz.prototype.testVoidMethodT('method<>(const char*)')); }; const testEnumerables = (obj, clazz) => { @@ -105,19 +118,19 @@ async function test(binding) { // for..in: object and prototype { const keys = []; - for (let key in obj) { + for (const key in obj) { keys.push(key); } - assert(keys.length == 6); + assert(keys.length === 6); // on prototype - assert(keys.includes("testGetSet")); - assert(keys.includes("testGetter")); - assert(keys.includes("testValue")); - assert(keys.includes("testMethod")); + assert(keys.includes('testGetSet')); + assert(keys.includes('testGetter')); + assert(keys.includes('testValue')); + assert(keys.includes('testMethod')); // on object only - assert(keys.includes("ownProperty")); - assert(keys.includes("ownPropertyT")); + assert(keys.includes('ownProperty')); + assert(keys.includes('ownPropertyT')); } }; @@ -133,7 +146,7 @@ async function test(binding) { obj.testSetter = 'iterator'; const values = []; - for (let item of obj) { + for (const item of obj) { values.push(item); } @@ -144,7 +157,7 @@ async function test(binding) { const testStaticValue = (clazz) => { assert.strictEqual(clazz.testStaticValue, 'value'); assert.strictEqual(clazz[clazz.kTestStaticValueInternal], 5); - } + }; const testStaticAccessor = (clazz) => { // read-only, write-only @@ -163,6 +176,7 @@ async function test(binding) { // read write-only { let error; + // eslint-disable-next-line no-unused-vars try { const read = clazz.testStaticSetter; } catch (e) { error = e; } // no error assert.strictEqual(error, undefined); @@ -205,6 +219,10 @@ async function test(binding) { }; const testStaticMethod = (clazz) => { + clazz.testStaticVoidMethod(52); + assert.strictEqual(clazz.testStaticGetter, 52); + clazz[clazz.kTestStaticVoidMethodInternal](94); + assert.strictEqual(clazz.testStaticGetter, 94); assert.strictEqual(clazz.testStaticMethod('method'), 'method static'); assert.strictEqual(clazz[clazz.kTestStaticMethodInternal]('method'), 'method static internal'); clazz.testStaticVoidMethodT('static method<>(const char*)'); @@ -219,13 +237,14 @@ async function test(binding) { 'testStaticValue', 'testStaticGetter', 'testStaticGetSet', - 'testStaticMethod' + 'testStaticMethod', + 'canUnWrap' ]); // for..in { const keys = []; - for (let key in clazz) { + for (const key in clazz) { keys.push(key); } @@ -233,25 +252,32 @@ async function test(binding) { 'testStaticValue', 'testStaticGetter', 'testStaticGetSet', - 'testStaticMethod' + 'testStaticMethod', + 'canUnWrap' ]); } }; - async function testFinalize(clazz) { + async function testFinalize (clazz) { let finalizeCalled = false; await testUtil.runGCTests([ 'test finalize', () => { - const finalizeCb = function(called) { + const finalizeCb = function (called) { finalizeCalled = called; }; - //Scope Test instance so that it can be gc'd. + // Scope Test instance so that it can be gc'd. + // eslint-disable-next-line no-new (() => { new Test(finalizeCb); })(); }, () => assert.strictEqual(finalizeCalled, true) ]); + } + + const testUnwrap = (obj, clazz) => { + obj.testSetter = 'unwrapTest'; + assert(clazz.canUnWrap(obj, 'unwrapTest')); }; const testObj = (obj, clazz) => { @@ -262,16 +288,17 @@ async function test(binding) { testEnumerables(obj, clazz); testConventions(obj, clazz); - } + testUnwrap(obj, clazz); + }; - async function testClass(clazz) { + async function testClass (clazz) { testStaticValue(clazz); testStaticAccessor(clazz); testStaticMethod(clazz); testStaticEnumerables(clazz); await testFinalize(clazz); - }; + } // `Test` is needed for accessing exposed symbols testObj(new Test(), Test); @@ -280,6 +307,3 @@ async function test(binding) { // Make sure the C++ object can be garbage collected without issues. await testUtil.runGCTests(['one last gc', () => {}, () => {}]); } - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); diff --git a/test/objectwrap_constructor_exception.cc b/test/objectwrap_constructor_exception.cc index d7e1bd517..266135547 100644 --- a/test/objectwrap_constructor_exception.cc +++ b/test/objectwrap_constructor_exception.cc @@ -1,10 +1,10 @@ #include -class ConstructorExceptionTest : - public Napi::ObjectWrap { -public: - ConstructorExceptionTest(const Napi::CallbackInfo& info) : - Napi::ObjectWrap(info) { +class ConstructorExceptionTest + : public Napi::ObjectWrap { + public: + ConstructorExceptionTest(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { Napi::Error error = Napi::Error::New(info.Env(), "an exception"); #ifdef NAPI_DISABLE_CPP_EXCEPTIONS error.ThrowAsJavaScriptException(); diff --git a/test/objectwrap_constructor_exception.js b/test/objectwrap_constructor_exception.js index 02dff2c48..2fbed8144 100644 --- a/test/objectwrap_constructor_exception.js +++ b/test/objectwrap_constructor_exception.js @@ -1,9 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -function test(binding) { +function test (binding) { return testUtil.runGCTests([ 'objectwrap constructor exception', () => { @@ -15,5 +15,4 @@ function test(binding) { ]); } -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); diff --git a/test/objectwrap_function.cc b/test/objectwrap_function.cc new file mode 100644 index 000000000..0ce074c68 --- /dev/null +++ b/test/objectwrap_function.cc @@ -0,0 +1,43 @@ +#include +#include +#include "test_helper.h" + +class FunctionTest : public Napi::ObjectWrap { + public: + FunctionTest(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) {} + + static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& info) { + // If called with a "true" argument, throw an exeption to test the handling. + if (!info[0].IsUndefined() && MaybeUnwrap(info[0].ToBoolean())) { + NAPI_THROW(Napi::Error::New(info.Env(), "an exception"), Napi::Value()); + } + // Otherwise, act as a factory. + std::vector args; + for (size_t i = 0; i < info.Length(); i++) args.push_back(info[i]); + return MaybeUnwrap(GetConstructor(info.Env()).New(args)); + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + const char* name = "FunctionTest"; + Napi::Function func = DefineClass(env, name, {}); + Napi::FunctionReference* ctor = new Napi::FunctionReference(); + *ctor = Napi::Persistent(func); + env.SetInstanceData(ctor); + exports.Set(name, func); + } + + static Napi::Function GetConstructor(Napi::Env env) { + return env.GetInstanceData()->Value(); + } +}; + +Napi::Value ObjectWrapFunctionFactory(const Napi::CallbackInfo& info) { + Napi::Object exports = Napi::Object::New(info.Env()); + FunctionTest::Initialize(info.Env(), exports); + return exports; +} + +Napi::Object InitObjectWrapFunction(Napi::Env env) { + return Napi::Function::New(env, "FunctionFactory"); +} diff --git a/test/objectwrap_function.js b/test/objectwrap_function.js new file mode 100644 index 000000000..671833911 --- /dev/null +++ b/test/objectwrap_function.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = require('./common').runTestInChildProcess({ + suite: 'objectwrap_function', + testName: 'runTest' +}); diff --git a/test/objectwrap_multiple_inheritance.cc b/test/objectwrap_multiple_inheritance.cc index 67913eb4e..30daaba43 100644 --- a/test/objectwrap_multiple_inheritance.cc +++ b/test/objectwrap_multiple_inheritance.cc @@ -1,25 +1,25 @@ #include class TestMIBase { -public: + public: TestMIBase() : test(0) {} virtual void dummy() {} uint32_t test; }; class TestMI : public TestMIBase, public Napi::ObjectWrap { -public: - TestMI(const Napi::CallbackInfo& info) : - Napi::ObjectWrap(info) {} + public: + TestMI(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} Napi::Value GetTest(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), test); } static void Initialize(Napi::Env env, Napi::Object exports) { - exports.Set("TestMI", DefineClass(env, "TestMI", { - InstanceAccessor<&TestMI::GetTest>("test") - })); + exports.Set( + "TestMI", + DefineClass( + env, "TestMI", {InstanceAccessor<&TestMI::GetTest>("test")})); } }; diff --git a/test/objectwrap_multiple_inheritance.js b/test/objectwrap_multiple_inheritance.js index 87c669eb3..526490478 100644 --- a/test/objectwrap_multiple_inheritance.js +++ b/test/objectwrap_multiple_inheritance.js @@ -1,6 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const test = bindingName => { @@ -9,7 +8,6 @@ const test = bindingName => { const testmi = new TestMI(); assert.strictEqual(testmi.test, 0); -} +}; -test(`./build/${buildType}/binding.node`); -test(`./build/${buildType}/binding_noexcept.node`); +module.exports = require('./common').runTestWithBindingPath(test); diff --git a/test/objectwrap-removewrap.cc b/test/objectwrap_removewrap.cc similarity index 98% rename from test/objectwrap-removewrap.cc rename to test/objectwrap_removewrap.cc index fdcec07c7..a714186f0 100644 --- a/test/objectwrap-removewrap.cc +++ b/test/objectwrap_removewrap.cc @@ -1,5 +1,5 @@ -#include #include +#include namespace { @@ -18,7 +18,7 @@ Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { } class Test : public Napi::ObjectWrap { -public: + public: Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { #ifdef NAPI_CPP_EXCEPTIONS throw Napi::Error::New(Env(), "Some error"); @@ -32,7 +32,7 @@ class Test : public Napi::ObjectWrap { exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled)); } -private: + private: DtorCounter dtor_counter_; }; diff --git a/test/objectwrap_removewrap.js b/test/objectwrap_removewrap.js new file mode 100644 index 000000000..012ebc482 --- /dev/null +++ b/test/objectwrap_removewrap.js @@ -0,0 +1,32 @@ +'use strict'; + +if (process.argv[2] === 'child') { + // Create a single wrapped instance then exit. + // eslint-disable-next-line no-new + new (require(process.argv[3]).objectwrap.Test)(); +} else { + const assert = require('assert'); + const testUtil = require('./testUtil'); + + module.exports = require('./common').runTestWithBindingPath(test); + + function test (bindingName) { + return testUtil.runGCTests([ + 'objectwrap removewrap test', + () => { + const binding = require(bindingName); + const Test = binding.objectwrap_removewrap.Test; + const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; + + assert.strictEqual(getDtorCalled(), 0); + assert.throws(() => { + // eslint-disable-next-line no-new + new Test(); + }); + assert.strictEqual(getDtorCalled(), 1); + }, + // Test that gc does not crash. + () => {} + ]); + } +} diff --git a/test/objectwrap_worker_thread.js b/test/objectwrap_worker_thread.js index 5e5e50b7e..59dfb9c0b 100644 --- a/test/objectwrap_worker_thread.js +++ b/test/objectwrap_worker_thread.js @@ -1,15 +1,20 @@ 'use strict'; -const { Worker, isMainThread, workerData } = require('worker_threads'); +const path = require('path'); +const { Worker, isMainThread } = require('worker_threads'); +const { runTestWithBuildType, whichBuildType } = require('./common'); -if (isMainThread) { - const buildType = process.config.target_defaults.default_configuration; - new Worker(__filename, { workerData: buildType }); -} else { - const test = binding => { - new binding.objectwrap.Test(); - }; +module.exports = runTestWithBuildType(test); - const buildType = workerData; - test(require(`./build/${buildType}/binding.node`)); - test(require(`./build/${buildType}/binding_noexcept.node`)); +async function test () { + if (isMainThread) { + const buildType = await whichBuildType(); + const worker = new Worker(__filename, { workerData: buildType }); + return new Promise((resolve, reject) => { + worker.on('exit', () => { + resolve(); + }); + }, () => {}); + } else { + await require(path.join(__dirname, 'objectwrap.js')); + } } diff --git a/test/promise.cc b/test/promise.cc index afef7c8fb..06ba22b86 100644 --- a/test/promise.cc +++ b/test/promise.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -18,12 +19,93 @@ Value RejectPromise(const CallbackInfo& info) { return deferred.Promise(); } +Value PromiseReturnsCorrectEnv(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + return Boolean::New(info.Env(), deferred.Env() == info.Env()); +} + +Value ThenMethodOnFulfilled(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedResolve(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = + MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedReject(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = + MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value CatchMethod(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onRejected = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Catch(onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + Object InitPromise(Env env) { Object exports = Object::New(env); exports["isPromise"] = Function::New(env, IsPromise); exports["resolvePromise"] = Function::New(env, ResolvePromise); exports["rejectPromise"] = Function::New(env, RejectPromise); + exports["promiseReturnsCorrectEnv"] = + Function::New(env, PromiseReturnsCorrectEnv); + exports["thenMethodOnFulfilled"] = Function::New(env, ThenMethodOnFulfilled); + exports["thenMethodOnFulfilledOnRejectedResolve"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedResolve); + exports["thenMethodOnFulfilledOnRejectedReject"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedReject); + exports["catchMethod"] = Function::New(env, CatchMethod); return exports; } diff --git a/test/promise.js b/test/promise.js index 65544c648..e61a783ce 100644 --- a/test/promise.js +++ b/test/promise.js @@ -1,12 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const common = require('./common'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); -async function test(binding) { +async function test (binding) { assert.strictEqual(binding.promise.isPromise({}), false); const resolving = binding.promise.resolvePromise('resolved'); @@ -16,4 +15,29 @@ async function test(binding) { const rejecting = binding.promise.rejectPromise('error'); await assert.strictEqual(binding.promise.isPromise(rejecting), true); rejecting.then(common.mustNotCall()).catch(common.mustCall()); + + assert(binding.promise.promiseReturnsCorrectEnv()); + + const onFulfilled = (value) => value * 2; + const onRejected = (reason) => reason + '!'; + + const thenOnFulfilled = binding.promise.thenMethodOnFulfilled(onFulfilled); + assert.strictEqual(thenOnFulfilled.isPromise, true); + const onFulfilledValue = await thenOnFulfilled.promise; + assert.strictEqual(onFulfilledValue, 84); + + const thenResolve = binding.promise.thenMethodOnFulfilledOnRejectedResolve(onFulfilled, onRejected); + assert.strictEqual(thenResolve.isPromise, true); + const thenResolveValue = await thenResolve.promise; + assert.strictEqual(thenResolveValue, 84); + + const thenRejected = binding.promise.thenMethodOnFulfilledOnRejectedReject(onFulfilled, onRejected); + assert.strictEqual(thenRejected.isPromise, true); + const rejectedValue = await thenRejected.promise; + assert.strictEqual(rejectedValue, 'Rejected!'); + + const catchMethod = binding.promise.catchMethod(onRejected); + assert.strictEqual(catchMethod.isPromise, true); + const catchValue = await catchMethod.promise; + assert.strictEqual(catchValue, 'Rejected!'); } diff --git a/test/reference.cc b/test/reference.cc index f93263295..b83c434a4 100644 --- a/test/reference.cc +++ b/test/reference.cc @@ -1,10 +1,61 @@ +#include "assert.h" #include "napi.h" - +#include "test_helper.h" using namespace Napi; static Reference> weak; -void CreateWeakArray(const CallbackInfo& info) { +static void RefMoveAssignTests(const Napi::CallbackInfo& info) { + Napi::Object obj = Napi::Object::New(info.Env()); + obj.Set("tPro", "tTEST"); + Napi::Reference ref = Napi::Reference::New(obj); + ref.SuppressDestruct(); + + napi_ref obj_ref = static_cast(ref); + Napi::Reference existingRef = + Napi::Reference(info.Env(), obj_ref); + assert(ref == existingRef); + assert(!(ref != existingRef)); + + std::string val = + MaybeUnwrap(existingRef.Value().Get("tPro")).As(); + assert(val == "tTEST"); + // ------------------------------------------------------------ // + Napi::Reference copyMoveRef = std::move(existingRef); + assert(copyMoveRef == ref); + + Napi::Reference copyAssignRef; + copyAssignRef = std::move(copyMoveRef); + assert(copyAssignRef == ref); +} + +static void ReferenceRefTests(const Napi::CallbackInfo& info) { + Napi::Object obj = Napi::Object::New(info.Env()); + Napi::Reference ref = Napi::Reference::New(obj); + + assert(ref.Ref() == 1); + assert(ref.Unref() == 0); +} + +static void ReferenceResetTests(const Napi::CallbackInfo& info) { + Napi::Object obj = Napi::Object::New(info.Env()); + Napi::Reference ref = Napi::Reference::New(obj); + assert(!ref.IsEmpty()); + + ref.Reset(); + assert(ref.IsEmpty()); + + Napi::Object newObject = Napi::Object::New(info.Env()); + newObject.Set("n-api", "node"); + + ref.Reset(newObject, 1); + assert(!ref.IsEmpty()); + + std::string val = MaybeUnwrap(ref.Value().Get("n-api")).As(); + assert(val == "node"); +} + +void CreateWeakArray(const CallbackInfo& info) { weak = Weak(Buffer::New(info.Env(), 1)); weak.SuppressDestruct(); } @@ -20,5 +71,8 @@ Object InitReference(Env env) { exports["createWeakArray"] = Function::New(env, CreateWeakArray); exports["accessWeakArrayEmpty"] = Function::New(env, AccessWeakArrayEmpty); + exports["refMoveAssignTest"] = Function::New(env, RefMoveAssignTests); + exports["referenceRefTest"] = Function::New(env, ReferenceRefTests); + exports["refResetTest"] = Function::New(env, ReferenceResetTests); return exports; } diff --git a/test/reference.js b/test/reference.js index 22ee8c842..2b4c4037f 100644 --- a/test/reference.js +++ b/test/reference.js @@ -1,17 +1,20 @@ 'use strict'; - -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { return testUtil.runGCTests([ 'test reference', () => binding.reference.createWeakArray(), - () => assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()) + () => assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()), + 'test reference move op', + () => binding.reference.refMoveAssignTest(), + 'test reference ref', + () => binding.reference.referenceRefTest(), + 'test reference reset', + () => binding.reference.refResetTest() ]); -}; +} diff --git a/test/require_basic_finalizers/index.js b/test/require_basic_finalizers/index.js new file mode 100644 index 000000000..31ee4f00b --- /dev/null +++ b/test/require_basic_finalizers/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const { promisify } = require('util'); +const exec = promisify(require('child_process').exec); +const { copy, remove } = require('fs-extra'); +const path = require('path'); +const assert = require('assert'); + +async function test () { + const addon = 'require-basic-finalizers'; + const ADDON_FOLDER = path.join(__dirname, 'addons', addon); + + await remove(ADDON_FOLDER); + await copy(path.join(__dirname, 'tpl'), ADDON_FOLDER); + + console.log(' >Building addon'); + + // Fail when NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS is enabled + await assert.rejects(exec('npm --require-basic-finalizers install', { + cwd: ADDON_FOLDER + }), 'Addon unexpectedly compiled successfully'); + + // Succeed when NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS is not enabled + return assert.doesNotReject(exec('npm install', { + cwd: ADDON_FOLDER + })); +} + +module.exports = (function () { + // This test will only run under an experimental version test. + const isExperimental = Number(process.env.NAPI_VERSION) === 2147483647; + + if (isExperimental) { + return test(); + } else { + console.log(' >Skipped (non-experimental test run)'); + } +})(); diff --git a/test/require_basic_finalizers/tpl/.npmrc b/test/require_basic_finalizers/tpl/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/test/require_basic_finalizers/tpl/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/test/require_basic_finalizers/tpl/addon.cc b/test/require_basic_finalizers/tpl/addon.cc new file mode 100644 index 000000000..f4277ac74 --- /dev/null +++ b/test/require_basic_finalizers/tpl/addon.cc @@ -0,0 +1,12 @@ +#include + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set( + "external", + Napi::External::New( + env, new int(1), [](Napi::Env /*env*/, int* data) { delete data; })); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/require_basic_finalizers/tpl/binding.gyp b/test/require_basic_finalizers/tpl/binding.gyp new file mode 100644 index 000000000..caf99d21f --- /dev/null +++ b/test/require_basic_finalizers/tpl/binding.gyp @@ -0,0 +1,48 @@ +{ + 'target_defaults': { + 'include_dirs': [ + "()); + return MaybeUnwrapOr(env.RunScript(info[0].UnsafeAs()), Value()); } Value RunWithContext(const CallbackInfo& info) { Env env = info.Env(); - Array keys = info[1].As().GetPropertyNames(); + Array keys = MaybeUnwrap(info[1].As().GetPropertyNames()); std::string code = "("; for (unsigned int i = 0; i < keys.Length(); i++) { if (i != 0) code += ","; - code += keys.Get(i).As().Utf8Value(); + code += MaybeUnwrap(keys.Get(i)).As().Utf8Value(); } code += ") => " + info[0].As().Utf8Value(); - Value ret = env.RunScript(code); + Value ret = MaybeUnwrap(env.RunScript(code)); Function fn = ret.As(); std::vector args; for (unsigned int i = 0; i < keys.Length(); i++) { - Value key = keys.Get(i); - args.push_back(info[1].As().Get(key)); + Value key = MaybeUnwrap(keys.Get(i)); + args.push_back(MaybeUnwrap(info[1].As().Get(key))); } - return fn.Call(args); + return MaybeUnwrap(fn.Call(args)); } -} // end anonymous namespace +} // end anonymous namespace Object InitRunScript(Env env) { Object exports = Object::New(env); diff --git a/test/run_script.js b/test/run_script.js index ec36dcf51..81b5879bd 100644 --- a/test/run_script.js +++ b/test/run_script.js @@ -1,12 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { return testUtil.runGCTests([ 'Plain C string', () => { @@ -22,7 +21,7 @@ function test(binding) { 'JavaScript string', () => { - const sum = binding.run_script.jsString("1 + 2 + 3"); + const sum = binding.run_script.jsString('1 + 2 + 3'); assert.strictEqual(sum, 1 + 2 + 3); }, @@ -31,15 +30,15 @@ function test(binding) { assert.throws(() => { binding.run_script.jsString(true); }, { - name: 'Error', + name: 'TypeError', message: 'A string was expected' }); }, 'With context', () => { - const a = 1, b = 2, c = 3; - const sum = binding.run_script.withContext("a + b + c", { a, b, c }); + const a = 1; const b = 2; const c = 3; + const sum = binding.run_script.withContext('a + b + c', { a, b, c }); assert.strictEqual(sum, a + b + c); } ]); diff --git a/test/shared_array_buffer.cc b/test/shared_array_buffer.cc new file mode 100644 index 000000000..57f66495a --- /dev/null +++ b/test/shared_array_buffer.cc @@ -0,0 +1,104 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value TestIsSharedArrayBuffer(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } + + return Boolean::New(info.Env(), info[0].IsSharedArrayBuffer()); +} + +Value TestCreateSharedArrayBuffer(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsNumber()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a number as first argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().Uint32Value(); + if (byte_length == 0) { + Error::New(info.Env(), + "Invalid byte length. Expects a non-negative integer.") + .ThrowAsJavaScriptException(); + return Value(); + } + + return SharedArrayBuffer::New(info.Env(), byte_length); +} + +Value TestGetSharedArrayBufferInfo(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsSharedArrayBuffer()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a SharedArrayBuffer as first " + "argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().ByteLength(); + + return Number::New(info.Env(), byte_length); +} + +Value TestSharedArrayBufferData(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsSharedArrayBuffer()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a SharedArrayBuffer as first " + "argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().ByteLength(); + void* data = info[0].As().Data(); + + if (byte_length > 0 && data != nullptr) { + uint8_t* bytes = static_cast(data); + for (size_t i = 0; i < byte_length; i++) { + bytes[i] = i % 256; + } + + return Boolean::New(info.Env(), true); + } + + return Boolean::New(info.Env(), false); +} +#endif +} // end anonymous namespace + +Object InitSharedArrayBuffer(Env env) { + Object exports = Object::New(env); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["testIsSharedArrayBuffer"] = + Function::New(env, TestIsSharedArrayBuffer); + exports["testCreateSharedArrayBuffer"] = + Function::New(env, TestCreateSharedArrayBuffer); + exports["testGetSharedArrayBufferInfo"] = + Function::New(env, TestGetSharedArrayBufferInfo); + exports["testSharedArrayBufferData"] = + Function::New(env, TestSharedArrayBufferData); +#endif + + return exports; +} diff --git a/test/shared_array_buffer.js b/test/shared_array_buffer.js new file mode 100644 index 000000000..018021ace --- /dev/null +++ b/test/shared_array_buffer.js @@ -0,0 +1,55 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + +let skippedMessageShown = false; + +function test ({ hasSharedArrayBuffer, sharedarraybuffer }) { + if (!hasSharedArrayBuffer) { + if (!skippedMessageShown) { + console.log(' >Skipped (no SharedArrayBuffer support)'); + skippedMessageShown = true; + } + return; + } + + { + const sab = new SharedArrayBuffer(16); + const ab = new ArrayBuffer(16); + const obj = {}; + const arr = []; + + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(sab), true); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(ab), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(obj), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(arr), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(null), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(undefined), false); + } + + { + const sab = sharedarraybuffer.testCreateSharedArrayBuffer(16); + assert(sab instanceof SharedArrayBuffer); + assert.strictEqual(sab.byteLength, 16); + } + + { + const sab = new SharedArrayBuffer(32); + const byteLength = sharedarraybuffer.testGetSharedArrayBufferInfo(sab); + assert.strictEqual(byteLength, 32); + } + + { + const sab = new SharedArrayBuffer(8); + const result = sharedarraybuffer.testSharedArrayBufferData(sab); + assert.strictEqual(result, true); + + // Check if data was written correctly + const view = new Uint8Array(sab); + for (let i = 0; i < 8; i++) { + assert.strictEqual(view[i], i % 256); + } + } +} diff --git a/test/symbol.cc b/test/symbol.cc new file mode 100644 index 000000000..d978739ff --- /dev/null +++ b/test/symbol.cc @@ -0,0 +1,91 @@ +#include + +#include + +#include "test_helper.h" +using namespace Napi; + +Symbol CreateNewSymbolWithNoArgs(const Napi::CallbackInfo&) { + return Napi::Symbol(); +} + +Symbol CreateNewSymbolWithCppStrDesc(const Napi::CallbackInfo& info) { + String cppStrKey = info[0].As(); + return Napi::Symbol::New(info.Env(), cppStrKey.Utf8Value()); +} + +Symbol CreateNewSymbolWithCStrDesc(const Napi::CallbackInfo& info) { + String cStrKey = info[0].As(); + return Napi::Symbol::New(info.Env(), cStrKey.Utf8Value().c_str()); +} + +Symbol CreateNewSymbolWithNapiString(const Napi::CallbackInfo& info) { + String strKey = info[0].As(); + return Napi::Symbol::New(info.Env(), strKey); +} + +Symbol GetWellknownSymbol(const Napi::CallbackInfo& info) { + String registrySymbol = info[0].As(); + return MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), registrySymbol.Utf8Value().c_str())); +} + +Symbol FetchSymbolFromGlobalRegistry(const Napi::CallbackInfo& info) { + String registrySymbol = info[0].As(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), registrySymbol)); +} + +Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value())); +} + +Symbol FetchSymbolFromGlobalRegistryWithStringViewKey( + const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + std::string key = cppStringKey.Utf8Value(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), std::string_view(key))); +} + +Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return MaybeUnwrap( + Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str())); +} + +Symbol TestUndefinedSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return MaybeUnwrap(Napi::Symbol::For(env, env.Undefined())); +} + +Symbol TestNullSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return MaybeUnwrap(Napi::Symbol::For(env, env.Null())); +} + +Object InitSymbol(Env env) { + Object exports = Object::New(env); + + exports["createNewSymbolWithNoArgs"] = + Function::New(env, CreateNewSymbolWithNoArgs); + exports["createNewSymbolWithCppStr"] = + Function::New(env, CreateNewSymbolWithCppStrDesc); + exports["createNewSymbolWithCStr"] = + Function::New(env, CreateNewSymbolWithCStrDesc); + exports["createNewSymbolWithNapi"] = + Function::New(env, CreateNewSymbolWithNapiString); + exports["getWellKnownSymbol"] = Function::New(env, GetWellknownSymbol); + exports["getSymbolFromGlobalRegistry"] = + Function::New(env, FetchSymbolFromGlobalRegistry); + exports["getSymbolFromGlobalRegistryWithCKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithCKey); + exports["getSymbolFromGlobalRegistryWithCppKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithCppKey); + exports["getSymbolFromGlobalRegistryWithStringViewKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithStringViewKey); + exports["testUndefinedSymbolCanBeCreated"] = + Function::New(env, TestUndefinedSymbolsCanBeCreated); + exports["testNullSymbolCanBeCreated"] = + Function::New(env, TestNullSymbolsCanBeCreated); + return exports; +} diff --git a/test/symbol.js b/test/symbol.js new file mode 100644 index 000000000..baf39c81b --- /dev/null +++ b/test/symbol.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + +function test (binding) { + const majorNodeVersion = process.versions.node.split('.')[0]; + + const wellKnownSymbolFunctions = ['asyncIterator', 'hasInstance', 'isConcatSpreadable', 'iterator', 'match', 'replace', 'search', 'split', 'species', 'toPrimitive', 'toStringTag', 'unscopables']; + if (majorNodeVersion >= 12) { + wellKnownSymbolFunctions.push('matchAll'); + } + + function assertCanCreateSymbol (symbol) { + assert(binding.symbol.createNewSymbolWithCppStr(symbol) !== null); + assert(binding.symbol.createNewSymbolWithCStr(symbol) !== null); + assert(binding.symbol.createNewSymbolWithNapi(symbol) !== null); + } + + function assertSymbolAreUnique (symbol) { + const symbolOne = binding.symbol.createNewSymbolWithCppStr(symbol); + const symbolTwo = binding.symbol.createNewSymbolWithCppStr(symbol); + + assert(symbolOne !== symbolTwo); + } + + function assertSymbolIsWellknown (symbol) { + const symbOne = binding.symbol.getWellKnownSymbol(symbol); + const symbTwo = binding.symbol.getWellKnownSymbol(symbol); + assert(symbOne && symbTwo); + assert(symbOne === symbTwo); + } + + function assertSymbolIsNotWellknown (symbol) { + const symbolTest = binding.symbol.getWellKnownSymbol(symbol); + assert(symbolTest === undefined); + } + + function assertCanCreateOrFetchGlobalSymbols (symbol, fetchFunction) { + const symbOne = fetchFunction(symbol); + const symbTwo = fetchFunction(symbol); + assert(symbOne && symbTwo); + assert(symbOne === symbTwo); + } + + assertCanCreateSymbol('testing'); + assertSymbolAreUnique('symbol'); + assertSymbolIsNotWellknown('testing'); + + for (const wellknownProperty of wellKnownSymbolFunctions) { + assertSymbolIsWellknown(wellknownProperty); + } + + assertCanCreateOrFetchGlobalSymbols('data', binding.symbol.getSymbolFromGlobalRegistry); + assertCanCreateOrFetchGlobalSymbols('CppKey', binding.symbol.getSymbolFromGlobalRegistryWithCppKey); + assertCanCreateOrFetchGlobalSymbols('StringViewKey', binding.symbol.getSymbolFromGlobalRegistryWithStringViewKey); + assertCanCreateOrFetchGlobalSymbols('CKey', binding.symbol.getSymbolFromGlobalRegistryWithCKey); + + assert(binding.symbol.createNewSymbolWithNoArgs() === undefined); + + // eslint-disable-next-line no-self-compare + assert(binding.symbol.testNullSymbolCanBeCreated() === binding.symbol.testNullSymbolCanBeCreated()); + // eslint-disable-next-line no-self-compare + assert(binding.symbol.testUndefinedSymbolCanBeCreated() === binding.symbol.testUndefinedSymbolCanBeCreated()); + assert(binding.symbol.testUndefinedSymbolCanBeCreated() !== binding.symbol.testNullSymbolCanBeCreated()); +} diff --git a/test/testUtil.js b/test/testUtil.js index b8777e781..470ba8144 100644 --- a/test/testUtil.js +++ b/test/testUtil.js @@ -1,9 +1,9 @@ // Run each test function in sequence, // with an async delay and GC call between each. -function tick(x) { +function tick (x) { return new Promise((resolve) => { - setImmediate(function ontick() { + setImmediate(function ontick () { if (--x === 0) { resolve(); } else { @@ -11,9 +11,9 @@ function tick(x) { } }); }); -}; +} -async function runGCTests(tests) { +async function runGCTests (tests) { // Break up test list into a list of lists of the form // [ [ 'test name', function() {}, ... ], ..., ]. const testList = []; @@ -27,7 +27,7 @@ async function runGCTests(tests) { } for (const test of testList) { - await (async function(test) { + await (async function (test) { let title; for (let i = 0; i < test.length; i++) { if (i === 0) { @@ -50,5 +50,5 @@ async function runGCTests(tests) { } module.exports = { - runGCTests, + runGCTests }; diff --git a/test/threadsafe_function/threadsafe_function.cc b/test/threadsafe_function/threadsafe_function.cc index e9b16083b..8902b73c5 100644 --- a/test/threadsafe_function/threadsafe_function.cc +++ b/test/threadsafe_function/threadsafe_function.cc @@ -1,4 +1,6 @@ #include +#include +#include #include #include "napi.h" @@ -10,84 +12,103 @@ constexpr size_t ARRAY_LENGTH = 10; constexpr size_t MAX_QUEUE_SIZE = 2; static std::thread threads[2]; -static ThreadSafeFunction tsfn; +static ThreadSafeFunction s_tsfn; struct ThreadSafeFunctionInfo { enum CallType { DEFAULT, BLOCKING, - NON_BLOCKING + NON_BLOCKING, + NON_BLOCKING_DEFAULT, + NON_BLOCKING_SINGLE_ARG } type; bool abort; bool startSecondary; FunctionReference jsFinalizeCallback; uint32_t maxQueueSize; + bool closeCalledFromJs; + std::mutex protect; + std::condition_variable signal; } tsfnInfo; // Thread data to transmit to JS static int ints[ARRAY_LENGTH]; static void SecondaryThread() { - if (tsfn.Release() != napi_ok) { + if (s_tsfn.Release() != napi_ok) { Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); } } // Source thread producing the data static void DataSourceThread() { - ThreadSafeFunctionInfo* info = tsfn.GetContext(); + ThreadSafeFunctionInfo* info = s_tsfn.GetContext(); if (info->startSecondary) { - if (tsfn.Acquire() != napi_ok) { + if (s_tsfn.Acquire() != napi_ok) { Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); } - threads[1] = std::thread(SecondaryThread); } bool queueWasFull = false; bool queueWasClosing = false; + for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) { napi_status status = napi_generic_failure; + auto callback = [](Env env, Function jsCallback, int* data) { - jsCallback.Call({ Number::New(env, *data) }); + jsCallback.Call({Number::New(env, *data)}); + }; + + auto noArgCallback = [](Env env, Function jsCallback) { + jsCallback.Call({Number::New(env, 42)}); }; switch (info->type) { case ThreadSafeFunctionInfo::DEFAULT: - status = tsfn.BlockingCall(); + status = s_tsfn.BlockingCall(); break; case ThreadSafeFunctionInfo::BLOCKING: - status = tsfn.BlockingCall(&ints[index], callback); + status = s_tsfn.BlockingCall(&ints[index], callback); break; case ThreadSafeFunctionInfo::NON_BLOCKING: - status = tsfn.NonBlockingCall(&ints[index], callback); + status = s_tsfn.NonBlockingCall(&ints[index], callback); + break; + case ThreadSafeFunctionInfo::NON_BLOCKING_DEFAULT: + status = s_tsfn.NonBlockingCall(); + break; + + case ThreadSafeFunctionInfo::NON_BLOCKING_SINGLE_ARG: + status = s_tsfn.NonBlockingCall(noArgCallback); break; } - if (info->maxQueueSize == 0) { - // Let's make this thread really busy for 200 ms to give the main thread a - // chance to abort. - auto start = std::chrono::high_resolution_clock::now(); - constexpr auto MS_200 = std::chrono::milliseconds(200); - for (; std::chrono::high_resolution_clock::now() - start < MS_200;); + if (info->abort && (info->type == ThreadSafeFunctionInfo::BLOCKING || + info->type == ThreadSafeFunctionInfo::DEFAULT)) { + // Let's make this thread really busy to give the main thread a chance to + // abort / close. + std::unique_lock lk(info->protect); + while (!info->closeCalledFromJs) { + info->signal.wait(lk); + } } switch (status) { - case napi_queue_full: - queueWasFull = true; - index++; - // fall through + case napi_queue_full: + queueWasFull = true; + index++; + // fall through - case napi_ok: - continue; + case napi_ok: + continue; - case napi_closing: - queueWasClosing = true; - break; + case napi_closing: + queueWasClosing = true; + break; - default: - Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); + default: + Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); } } @@ -99,7 +120,7 @@ static void DataSourceThread() { Error::Fatal("DataSourceThread", "Queue was never closing"); } - if (!queueWasClosing && tsfn.Release() != napi_ok) { + if (!queueWasClosing && s_tsfn.Release() != napi_ok) { Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); } } @@ -108,9 +129,14 @@ static Value StopThread(const CallbackInfo& info) { tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As()); bool abort = info[1].As(); if (abort) { - tsfn.Abort(); + s_tsfn.Abort(); } else { - tsfn.Release(); + s_tsfn.Release(); + } + { + std::lock_guard _(tsfnInfo.protect); + tsfnInfo.closeCalledFromJs = true; + tsfnInfo.signal.notify_one(); } return Value(); } @@ -129,14 +155,21 @@ static void JoinTheThreads(Env /* env */, } static Value StartThreadInternal(const CallbackInfo& info, - ThreadSafeFunctionInfo::CallType type) { + ThreadSafeFunctionInfo::CallType type) { tsfnInfo.type = type; tsfnInfo.abort = info[1].As(); tsfnInfo.startSecondary = info[2].As(); tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); + tsfnInfo.closeCalledFromJs = false; - tsfn = ThreadSafeFunction::New(info.Env(), info[0].As(), - "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, threads); + s_tsfn = ThreadSafeFunction::New(info.Env(), + info[0].As(), + "Test", + tsfnInfo.maxQueueSize, + 2, + &tsfnInfo, + JoinTheThreads, + threads); threads[0] = std::thread(DataSourceThread); @@ -144,7 +177,7 @@ static Value StartThreadInternal(const CallbackInfo& info, } static Value Release(const CallbackInfo& /* info */) { - if (tsfn.Release() != napi_ok) { + if (s_tsfn.Release() != napi_ok) { Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); } return Value(); @@ -162,6 +195,16 @@ static Value StartThreadNoNative(const CallbackInfo& info) { return StartThreadInternal(info, ThreadSafeFunctionInfo::DEFAULT); } +static Value StartThreadNonblockingNoNative(const CallbackInfo& info) { + return StartThreadInternal(info, + ThreadSafeFunctionInfo::NON_BLOCKING_DEFAULT); +} + +static Value StartThreadNonBlockingSingleArg(const CallbackInfo& info) { + return StartThreadInternal(info, + ThreadSafeFunctionInfo::NON_BLOCKING_SINGLE_ARG); +} + Object InitThreadSafeFunction(Env env) { for (size_t index = 0; index < ARRAY_LENGTH; index++) { ints[index] = index; @@ -172,8 +215,12 @@ Object InitThreadSafeFunction(Env env) { exports["MAX_QUEUE_SIZE"] = Number::New(env, MAX_QUEUE_SIZE); exports["startThread"] = Function::New(env, StartThread); exports["startThreadNoNative"] = Function::New(env, StartThreadNoNative); + exports["startThreadNonblockingNoNative"] = + Function::New(env, StartThreadNonblockingNoNative); exports["startThreadNonblocking"] = Function::New(env, StartThreadNonblocking); + exports["startThreadNonblockSingleArg"] = + Function::New(env, StartThreadNonBlockingSingleArg); exports["stopThread"] = Function::New(env, StopThread); exports["release"] = Function::New(env, Release); diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 419c214f8..b29dfadb1 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -1,16 +1,13 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = (async function() { - await test(require(`../build/${buildType}/binding.node`)); - await test(require(`../build/${buildType}/binding_noexcept.node`)); -})(); +module.exports = common.runTest(test); -async function test(binding) { - const expectedArray = (function(arrayLength) { +// Main test body +async function test (binding) { + const expectedArray = (function (arrayLength) { const result = []; for (let index = 0; index < arrayLength; index++) { result.push(arrayLength - 1 - index); @@ -18,25 +15,26 @@ async function test(binding) { return result; })(binding.threadsafe_function.ARRAY_LENGTH); - function testWithJSMarshaller({ + const expectedDefaultArray = Array.from({ length: binding.threadsafe_function.ARRAY_LENGTH }, (_, i) => 42); + + function testWithJSMarshaller ({ threadStarter, quitAfter, abort, maxQueueSize, - launchSecondary }) { + launchSecondary + }) { return new Promise((resolve) => { const array = []; - binding.threadsafe_function[threadStarter](function testCallback(value) { + binding.threadsafe_function[threadStarter](function testCallback (value) { array.push(value); if (array.length === quitAfter) { - setImmediate(() => { - binding.threadsafe_function.stopThread(common.mustCall(() => { - resolve(array); - }), !!abort); - }); + binding.threadsafe_function.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); } }, !!abort, !!launchSecondary, maxQueueSize); - if (threadStarter === 'startThreadNonblocking') { + if ((threadStarter === 'startThreadNonblocking' || threadStarter === 'startThreadNonblockSingleArg')) { // Let's make this thread really busy for a short while to ensure that // the queue fills and the thread receives a napi_queue_full. const start = Date.now(); @@ -45,23 +43,28 @@ async function test(binding) { }); } - await new Promise(function testWithoutJSMarshaller(resolve) { - let callCount = 0; - binding.threadsafe_function.startThreadNoNative(function testCallback() { - callCount++; + function testWithoutJSMarshallers (nativeFunction) { + return new Promise((resolve) => { + let callCount = 0; + nativeFunction(function testCallback () { + callCount++; - // The default call-into-JS implementation passes no arguments. - assert.strictEqual(arguments.length, 0); - if (callCount === binding.threadsafe_function.ARRAY_LENGTH) { - setImmediate(() => { - binding.threadsafe_function.stopThread(common.mustCall(() => { - resolve(); - }), false); - }); - } - }, false /* abort */, false /* launchSecondary */, - binding.threadsafe_function.MAX_QUEUE_SIZE); - }); + // The default call-into-JS implementation passes no arguments. + assert.strictEqual(arguments.length, 0); + if (callCount === binding.threadsafe_function.ARRAY_LENGTH) { + setImmediate(() => { + binding.threadsafe_function.stopThread(common.mustCall(() => { + resolve(); + }), false); + }); + } + }, false /* abort */, false /* launchSecondary */, + binding.threadsafe_function.MAX_QUEUE_SIZE); + }); + } + + await testWithoutJSMarshallers(binding.threadsafe_function.startThreadNoNative); + await testWithoutJSMarshallers(binding.threadsafe_function.startThreadNonblockingNoNative); // Start the thread in blocking mode, and assert that all values are passed. // Quit after it's done. @@ -71,7 +74,7 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, quitAfter: binding.threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -82,7 +85,7 @@ async function test(binding) { maxQueueSize: 0, quitAfter: binding.threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in non-blocking mode, and assert that all values are @@ -93,7 +96,7 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, quitAfter: binding.threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in blocking mode, and assert that all values are passed. @@ -104,7 +107,7 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, quitAfter: 1 }), - expectedArray, + expectedArray ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -115,10 +118,9 @@ async function test(binding) { maxQueueSize: 0, quitAfter: 1 }), - expectedArray, + expectedArray ); - // Start the thread in non-blocking mode, and assert that all values are // passed. Quit early, but let the thread finish. assert.deepStrictEqual( @@ -127,7 +129,16 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, quitAfter: 1 }), - expectedArray, + expectedArray + ); + + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblockSingleArg', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1 + }), + expectedDefaultArray ); // Start the thread in blocking mode, and assert that all values are passed. @@ -140,7 +151,7 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, launchSecondary: true }), - expectedArray, + expectedArray ); // Start the thread in non-blocking mode, and assert that all values are @@ -153,7 +164,17 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, launchSecondary: true }), - expectedArray, + expectedArray + ); + + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblockSingleArg', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1, + launchSecondary: true + }), + expectedDefaultArray ); // Start the thread in blocking mode, and assert that it could not finish. @@ -165,7 +186,7 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, abort: true })).indexOf(0), - -1, + -1 ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -177,7 +198,7 @@ async function test(binding) { maxQueueSize: 0, abort: true })).indexOf(0), - -1, + -1 ); // Start the thread in non-blocking mode, and assert that it could not finish. @@ -189,6 +210,16 @@ async function test(binding) { maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, abort: true })).indexOf(0), - -1, + -1 + ); + + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThreadNonblockSingleArg', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + abort: true + })).indexOf(0), + -1 ); } diff --git a/test/threadsafe_function/threadsafe_function_ctx.cc b/test/threadsafe_function/threadsafe_function_ctx.cc index bae83baa0..abe92fdf0 100644 --- a/test/threadsafe_function/threadsafe_function_ctx.cc +++ b/test/threadsafe_function/threadsafe_function_ctx.cc @@ -1,3 +1,4 @@ +#include #include "napi.h" #if (NAPI_VERSION > 3) @@ -7,57 +8,148 @@ using namespace Napi; namespace { class TSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); + public: + static Function Init(Napi::Env env); + TSFNWrap(const CallbackInfo& info); - Napi::Value GetContext(const CallbackInfo & /*info*/) { - Reference *ctx = _tsfn.GetContext(); + Napi::Value GetContext(const CallbackInfo& /*info*/) { + Reference* ctx = _tsfn.GetContext(); return ctx->Value(); }; - Napi::Value Release(const CallbackInfo &info) { + Napi::Value Release(const CallbackInfo& info) { Napi::Env env = info.Env(); _deferred = std::unique_ptr(new Promise::Deferred(env)); _tsfn.Release(); return _deferred->Promise(); }; -private: + private: ThreadSafeFunction _tsfn; std::unique_ptr _deferred; }; -Object TSFNWrap::Init(Napi::Env env, Object exports) { +Function TSFNWrap::Init(Napi::Env env) { Function func = - DefineClass(env, "TSFNWrap", + DefineClass(env, + "TSFNWrap", {InstanceMethod("getContext", &TSFNWrap::GetContext), InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; + return func; } -TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { +TSFNWrap::TSFNWrap(const CallbackInfo& info) : ObjectWrap(info) { Napi::Env env = info.Env(); - Reference *_ctx = new Reference; + Reference* _ctx = new Reference; *_ctx = Persistent(info[0]); _tsfn = ThreadSafeFunction::New( - info.Env(), Function::New(env, [](const CallbackInfo & /*info*/) {}), - Object::New(env), "Test", 1, 1, _ctx, - [this](Napi::Env env, Reference *ctx) { + info.Env(), + Function::New(env, [](const CallbackInfo& /*info*/) {}), + Object::New(env), + "Test", + 1, + 1, + _ctx, + [this](Napi::Env env, Reference* ctx) { _deferred->Resolve(env.Undefined()); ctx->Reset(); delete ctx; }); } +struct SimpleTestContext { + SimpleTestContext(int val) : _val(val) {} + int _val = -1; +}; + +void AssertGetContextFromVariousTSOverloads(const CallbackInfo& info) { + Env env = info.Env(); + Function emptyFunc; + + SimpleTestContext* ctx = new SimpleTestContext(42); + ThreadSafeFunction fn = + ThreadSafeFunction::New(env, emptyFunc, "testResource", 1, 1, ctx); + + assert(fn.GetContext() == ctx); + delete ctx; + fn.Release(); + + fn = ThreadSafeFunction::New(env, emptyFunc, "testRes", 1, 1, [](Env) {}); + fn.Release(); + + ctx = new SimpleTestContext(42); + fn = ThreadSafeFunction::New(env, + emptyFunc, + Object::New(env), + "resStrObj", + 1, + 1, + ctx, + [](Env, SimpleTestContext*) {}); + assert(fn.GetContext() == ctx); + delete ctx; + fn.Release(); + + fn = ThreadSafeFunction::New( + env, emptyFunc, Object::New(env), "resStrObj", 1, 1); + fn.Release(); + + ctx = new SimpleTestContext(42); + fn = ThreadSafeFunction::New( + env, emptyFunc, Object::New(env), "resStrObj", 1, 1, ctx); + assert(fn.GetContext() == ctx); + delete ctx; + fn.Release(); + + using FinalizerDataType = int; + FinalizerDataType* finalizerData = new int(42); + fn = ThreadSafeFunction::New( + env, + emptyFunc, + Object::New(env), + "resObject", + 1, + 1, + [](Env, FinalizerDataType* data) { + assert(*data == 42); + delete data; + }, + finalizerData); + fn.Release(); + + ctx = new SimpleTestContext(42); + FinalizerDataType* finalizerDataB = new int(42); + + fn = ThreadSafeFunction::New( + env, + emptyFunc, + Object::New(env), + "resObject", + 1, + 1, + ctx, + [](Env, FinalizerDataType* _data, SimpleTestContext* _ctx) { + assert(*_data == 42); + assert(_ctx->_val == 42); + delete _data; + delete _ctx; + }, + finalizerDataB); + assert(fn.GetContext() == ctx); + fn.Release(); +} -} // namespace +} // namespace Object InitThreadSafeFunctionCtx(Env env) { - return TSFNWrap::Init(env, Object::New(env)); + Object exports = Object::New(env); + Function tsfnWrap = TSFNWrap::Init(env); + exports.Set("TSFNWrap", tsfnWrap); + exports.Set("AssertFnReturnCorrectCxt", + Function::New(env, AssertGetContextFromVariousTSOverloads)); + + return exports; } #endif diff --git a/test/threadsafe_function/threadsafe_function_ctx.js b/test/threadsafe_function/threadsafe_function_ctx.js index 2651586a0..4ba707b79 100644 --- a/test/threadsafe_function/threadsafe_function_ctx.js +++ b/test/threadsafe_function/threadsafe_function_ctx.js @@ -1,14 +1,13 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); -async function test(binding) { +async function test (binding) { const ctx = { }; const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); assert(tsfn.getContext() === ctx); await tsfn.release(); + binding.threadsafe_function_ctx.AssertFnReturnCorrectCxt(); } diff --git a/test/threadsafe_function/threadsafe_function_exception.cc b/test/threadsafe_function/threadsafe_function_exception.cc new file mode 100644 index 000000000..9ffe703ec --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_exception.cc @@ -0,0 +1,50 @@ +#include +#include "napi.h" +#include "test_helper.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +void CallJS(napi_env env, napi_value /* callback */, void* /*data*/) { + Napi::Error error = Napi::Error::New(env, "test-from-native"); + NAPI_THROW_VOID(error); +} + +void TestCall(const CallbackInfo& info) { + Napi::Env env = info.Env(); + + ThreadSafeFunction wrapped = + ThreadSafeFunction::New(env, + info[0].As(), + Object::New(env), + String::New(env, "Test"), + 0, + 1); + wrapped.BlockingCall(static_cast(nullptr)); + wrapped.Release(); +} + +void TestCallWithNativeCallback(const CallbackInfo& info) { + Napi::Env env = info.Env(); + + ThreadSafeFunction wrapped = ThreadSafeFunction::New( + env, Napi::Function(), Object::New(env), String::New(env, "Test"), 0, 1); + wrapped.BlockingCall(static_cast(nullptr), CallJS); + wrapped.Release(); +} + +} // namespace + +Object InitThreadSafeFunctionException(Env env) { + Object exports = Object::New(env); + exports["testCall"] = Function::New(env, TestCall); + exports["testCallWithNativeCallback"] = + Function::New(env, TestCallWithNativeCallback); + + return exports; +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_exception.js b/test/threadsafe_function/threadsafe_function_exception.js new file mode 100644 index 000000000..688a53bcf --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_exception.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common'); + +module.exports = common.runTest(test); + +const execArgv = ['--force-node-api-uncaught-exceptions-policy=true']; +async function test () { + await common.runTestInChildProcess({ + suite: 'threadsafe_function_exception', + testName: 'testCall', + execArgv + }); + + await common.runTestInChildProcess({ + suite: 'threadsafe_function_exception', + testName: 'testCallWithNativeCallback', + execArgv + }); +} diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc index 19971b824..226493703 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ -#include "napi.h" #include +#include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -8,21 +9,20 @@ using namespace Napi; namespace { struct TestContext { - TestContext(Promise::Deferred &&deferred) + TestContext(Promise::Deferred&& deferred) : deferred(std::move(deferred)), callData(nullptr){}; napi_threadsafe_function tsfn; Promise::Deferred deferred; - double *callData; + double* callData; ~TestContext() { - if (callData != nullptr) - delete callData; + if (callData != nullptr) delete callData; }; }; -void FinalizeCB(napi_env env, void * /*finalizeData */, void *context) { - TestContext *testContext = static_cast(context); +void FinalizeCB(napi_env env, void* /*finalizeData */, void* context) { + TestContext* testContext = static_cast(context); if (testContext->callData != nullptr) { testContext->deferred.Resolve(Number::New(env, *testContext->callData)); } else { @@ -31,10 +31,12 @@ void FinalizeCB(napi_env env, void * /*finalizeData */, void *context) { delete testContext; } -void CallJSWithData(napi_env env, napi_value /* callback */, void *context, - void *data) { - TestContext *testContext = static_cast(context); - testContext->callData = static_cast(data); +void CallJSWithData(napi_env env, + napi_value /* callback */, + void* context, + void* data) { + TestContext* testContext = static_cast(context); + testContext->callData = static_cast(data); napi_status status = napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release); @@ -42,9 +44,11 @@ void CallJSWithData(napi_env env, napi_value /* callback */, void *context, NAPI_THROW_IF_FAILED_VOID(env, status); } -void CallJSNoData(napi_env env, napi_value /* callback */, void *context, - void * /*data*/) { - TestContext *testContext = static_cast(context); +void CallJSNoData(napi_env env, + napi_value /* callback */, + void* context, + void* /*data*/) { + TestContext* testContext = static_cast(context); testContext->callData = nullptr; napi_status status = @@ -53,30 +57,39 @@ void CallJSNoData(napi_env env, napi_value /* callback */, void *context, NAPI_THROW_IF_FAILED_VOID(env, status); } -static Value TestCall(const CallbackInfo &info) { +static Value TestCall(const CallbackInfo& info) { Napi::Env env = info.Env(); bool isBlocking = false; bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } // Allow optional callback passed from JS. Useful for testing. - Function cb = Function::New(env, [](const CallbackInfo & /*info*/) {}); + Function cb = Function::New(env, [](const CallbackInfo& /*info*/) {}); - TestContext *testContext = new TestContext(Napi::Promise::Deferred(env)); + TestContext* testContext = new TestContext(Napi::Promise::Deferred(env)); - napi_status status = napi_create_threadsafe_function( - env, cb, Object::New(env), String::New(env, "Test"), 0, 1, - nullptr, /*finalize data*/ - FinalizeCB, testContext, hasData ? CallJSWithData : CallJSNoData, - &testContext->tsfn); + napi_status status = + napi_create_threadsafe_function(env, + cb, + Object::New(env), + String::New(env, "Test"), + 0, + 1, + nullptr, /*finalize data*/ + FinalizeCB, + testContext, + hasData ? CallJSWithData : CallJSNoData, + &testContext->tsfn); NAPI_THROW_IF_FAILED(env, status, Value()); @@ -85,22 +98,22 @@ static Value TestCall(const CallbackInfo &info) { // Test the four napi_threadsafe_function direct-accessing calls if (isBlocking) { if (hasData) { - wrapped.BlockingCall(static_cast(new double(std::rand()))); + wrapped.BlockingCall(static_cast(new double(std::rand()))); } else { - wrapped.BlockingCall(static_cast(nullptr)); + wrapped.BlockingCall(static_cast(nullptr)); } } else { if (hasData) { - wrapped.NonBlockingCall(static_cast(new double(std::rand()))); + wrapped.NonBlockingCall(static_cast(new double(std::rand()))); } else { - wrapped.NonBlockingCall(static_cast(nullptr)); + wrapped.NonBlockingCall(static_cast(nullptr)); } } return testContext->deferred.Promise(); } -} // namespace +} // namespace Object InitThreadSafeFunctionExistingTsfn(Env env) { Object exports = Object::New(env); diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.js b/test/threadsafe_function/threadsafe_function_existing_tsfn.js index d5ab1854a..8de71f72c 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.js +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.js @@ -2,16 +2,13 @@ const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; +module.exports = require('../common').runTest(test); -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); - -async function test(binding) { +async function test (binding) { const testCall = binding.threadsafe_function_existing_tsfn.testCall; - - assert.strictEqual(typeof await testCall({ blocking: true, data: true }), "number"); - assert.strictEqual(typeof await testCall({ blocking: true, data: false }), "undefined"); - assert.strictEqual(typeof await testCall({ blocking: false, data: true }), "number"); - assert.strictEqual(typeof await testCall({ blocking: false, data: false }), "undefined"); + + assert.strictEqual(typeof await testCall({ blocking: true, data: true }), 'number'); + assert.strictEqual(typeof await testCall({ blocking: true, data: false }), 'undefined'); + assert.strictEqual(typeof await testCall({ blocking: false, data: true }), 'number'); + assert.strictEqual(typeof await testCall({ blocking: false, data: false }), 'undefined'); } diff --git a/test/threadsafe_function/threadsafe_function_ptr.cc b/test/threadsafe_function/threadsafe_function_ptr.cc index 00e8559b8..4a1df1487 100644 --- a/test/threadsafe_function/threadsafe_function_ptr.cc +++ b/test/threadsafe_function/threadsafe_function_ptr.cc @@ -9,12 +9,13 @@ namespace { static Value Test(const CallbackInfo& info) { Object resource = info[0].As(); Function cb = info[1].As(); - ThreadSafeFunction tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1); + ThreadSafeFunction tsfn = + ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1); tsfn.Release(); return info.Env().Undefined(); } -} +} // namespace Object InitThreadSafeFunctionPtr(Env env) { Object exports = Object::New(env); diff --git a/test/threadsafe_function/threadsafe_function_ptr.js b/test/threadsafe_function/threadsafe_function_ptr.js index 535b5d642..d646682d1 100644 --- a/test/threadsafe_function/threadsafe_function_ptr.js +++ b/test/threadsafe_function/threadsafe_function_ptr.js @@ -1,10 +1,7 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; +module.exports = require('../common').runTest(test); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - -function test(binding) { +function test (binding) { binding.threadsafe_function_ptr.test({}, () => {}); } diff --git a/test/threadsafe_function/threadsafe_function_sum.cc b/test/threadsafe_function/threadsafe_function_sum.cc index 5134e5816..68e72fb7c 100644 --- a/test/threadsafe_function/threadsafe_function_sum.cc +++ b/test/threadsafe_function/threadsafe_function_sum.cc @@ -1,8 +1,8 @@ -#include "napi.h" -#include -#include #include +#include #include +#include +#include "napi.h" #if (NAPI_VERSION > 3) @@ -11,9 +11,8 @@ using namespace Napi; namespace { struct TestData { + TestData(Promise::Deferred&& deferred) : deferred(std::move(deferred)){}; - TestData(Promise::Deferred&& deferred) : deferred(std::move(deferred)) {}; - // Native Promise returned to JavaScript Promise::Deferred deferred; @@ -28,7 +27,7 @@ struct TestData { size_t expected_calls = 0; }; -void FinalizerCallback(Napi::Env env, TestData* finalizeData){ +void FinalizerCallback(Napi::Env env, TestData* finalizeData) { for (size_t i = 0; i < finalizeData->threads.size(); ++i) { finalizeData->threads[i].join(); } @@ -42,8 +41,8 @@ void FinalizerCallback(Napi::Env env, TestData* finalizeData){ void entryWithTSFN(ThreadSafeFunction tsfn, int threadId) { std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); - tsfn.BlockingCall( [=](Napi::Env env, Function callback) { - callback.Call( { Number::New(env, static_cast(threadId))}); + tsfn.BlockingCall([=](Napi::Env env, Function callback) { + callback.Call({Number::New(env, static_cast(threadId))}); }); tsfn.Release(); } @@ -54,15 +53,20 @@ static Value TestWithTSFN(const CallbackInfo& info) { // We pass the test data to the Finalizer for cleanup. The finalizer is // responsible for deleting this data as well. - TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + TestData* testData = new TestData(Promise::Deferred::New(info.Env())); ThreadSafeFunction tsfn = ThreadSafeFunction::New( - info.Env(), cb, "Test", 0, threadCount, - std::function(FinalizerCallback), testData); + info.Env(), + cb, + "Test", + 0, + threadCount, + std::function(FinalizerCallback), + testData); for (int i = 0; i < threadCount; ++i) { // A copy of the ThreadSafeFunction will go to the thread entry point - testData->threads.push_back( std::thread(entryWithTSFN, tsfn, i) ); + testData->threads.push_back(std::thread(entryWithTSFN, tsfn, i)); } return testData->deferred.Promise(); @@ -70,7 +74,7 @@ static Value TestWithTSFN(const CallbackInfo& info) { // Task instance created for each new std::thread class DelayedTSFNTask { -public: + public: // Each instance has its own tsfn ThreadSafeFunction tsfn; @@ -81,7 +85,7 @@ class DelayedTSFNTask { // Entry point for std::thread void entryDelayedTSFN(int threadId) { std::unique_lock lk(mtx); - cv.wait(lk); + cv.wait(lk, [this] { return this->tsfn != nullptr; }); tsfn.BlockingCall([=](Napi::Env env, Function callback) { callback.Call({Number::New(env, static_cast(threadId))}); }); @@ -90,8 +94,7 @@ class DelayedTSFNTask { }; struct TestDataDelayed { - - TestDataDelayed(Promise::Deferred &&deferred) + TestDataDelayed(Promise::Deferred&& deferred) : deferred(std::move(deferred)){}; ~TestDataDelayed() { taskInsts.clear(); }; // Native Promise returned to JavaScript @@ -107,7 +110,7 @@ struct TestDataDelayed { ThreadSafeFunction tsfn = ThreadSafeFunction(); }; -void FinalizerCallbackDelayed(Napi::Env env, TestDataDelayed *finalizeData) { +void FinalizerCallbackDelayed(Napi::Env env, TestDataDelayed* finalizeData) { for (size_t i = 0; i < finalizeData->threads.size(); ++i) { finalizeData->threads[i].join(); } @@ -115,15 +118,19 @@ void FinalizerCallbackDelayed(Napi::Env env, TestDataDelayed *finalizeData) { delete finalizeData; } -static Value TestDelayedTSFN(const CallbackInfo &info) { +static Value TestDelayedTSFN(const CallbackInfo& info) { int threadCount = info[0].As().Int32Value(); Function cb = info[1].As(); - TestDataDelayed *testData = + TestDataDelayed* testData = new TestDataDelayed(Promise::Deferred::New(info.Env())); testData->tsfn = - ThreadSafeFunction::New(info.Env(), cb, "Test", 0, threadCount, + ThreadSafeFunction::New(info.Env(), + cb, + "Test", + 0, + threadCount, std::function( FinalizerCallbackDelayed), testData); @@ -137,7 +144,7 @@ static Value TestDelayedTSFN(const CallbackInfo &info) { } std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); - for (auto &task : testData->taskInsts) { + for (auto& task : testData->taskInsts) { std::lock_guard lk(task->mtx); task->tsfn = testData->tsfn; task->cv.notify_all(); @@ -149,7 +156,7 @@ static Value TestDelayedTSFN(const CallbackInfo &info) { void AcquireFinalizerCallback(Napi::Env env, TestData* finalizeData, TestData* context) { - (void) context; + (void)context; for (size_t i = 0; i < finalizeData->threads.size(); ++i) { finalizeData->threads[i].join(); } @@ -161,13 +168,13 @@ void entryAcquire(ThreadSafeFunction tsfn, int threadId) { tsfn.Acquire(); TestData* testData = tsfn.GetContext(); std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); - tsfn.BlockingCall( [=](Napi::Env env, Function callback) { + tsfn.BlockingCall([=](Napi::Env env, Function callback) { // This lambda runs on the main thread so it's OK to access the variables // `expected_calls` and `mainWantsRelease`. testData->expected_calls--; if (testData->expected_calls == 0 && testData->mainWantsRelease) testData->tsfn.Release(); - callback.Call( { Number::New(env, static_cast(threadId))}); + callback.Call({Number::New(env, static_cast(threadId))}); }); tsfn.Release(); } @@ -182,7 +189,7 @@ static Value CreateThread(const CallbackInfo& info) { ThreadSafeFunction tsfn = testData->tsfn; int threadId = testData->threads.size(); // A copy of the ThreadSafeFunction will go to the thread entry point - testData->threads.push_back( std::thread(entryAcquire, tsfn, threadId) ); + testData->threads.push_back(std::thread(entryAcquire, tsfn, threadId)); return Number::New(info.Env(), threadId); } @@ -198,21 +205,29 @@ static Value TestAcquire(const CallbackInfo& info) { // We pass the test data to the Finalizer for cleanup. The finalizer is // responsible for deleting this data as well. - TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + TestData* testData = new TestData(Promise::Deferred::New(info.Env())); - testData->tsfn = ThreadSafeFunction::New( - env, cb, "Test", 0, 1, testData, - std::function(AcquireFinalizerCallback), - testData); + testData->tsfn = + ThreadSafeFunction::New(env, + cb, + "Test", + 0, + 1, + testData, + std::function( + AcquireFinalizerCallback), + testData); Object result = Object::New(env); - result["createThread"] = Function::New( env, CreateThread, "createThread", testData); - result["stopThreads"] = Function::New( env, StopThreads, "stopThreads", testData); + result["createThread"] = + Function::New(env, CreateThread, "createThread", testData); + result["stopThreads"] = + Function::New(env, StopThreads, "stopThreads", testData); result["promise"] = testData->deferred.Promise(); return result; } -} +} // namespace Object InitThreadSafeFunctionSum(Env env) { Object exports = Object::New(env); diff --git a/test/threadsafe_function/threadsafe_function_sum.js b/test/threadsafe_function/threadsafe_function_sum.js index 738e31db2..63225449d 100644 --- a/test/threadsafe_function/threadsafe_function_sum.js +++ b/test/threadsafe_function/threadsafe_function_sum.js @@ -1,6 +1,5 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; /** * @@ -29,21 +28,20 @@ const buildType = process.config.target_defaults.default_configuration; const THREAD_COUNT = 5; const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); /** @param {number[]} N */ const sum = (N) => N.reduce((sum, n) => sum + n, 0); -function test(binding) { - async function check(bindingFunction) { +function test (binding) { + async function check (bindingFunction) { const calls = []; const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); assert.ok(result); assert.equal(sum(calls), EXPECTED_SUM); } - async function checkAcquire() { + async function checkAcquire () { const calls = []; const { promise, createThread, stopThreads } = binding.threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); for (let i = 0; i < THREAD_COUNT; i++) { diff --git a/test/threadsafe_function/threadsafe_function_unref.cc b/test/threadsafe_function/threadsafe_function_unref.cc index 6877e50f6..5fcc5dd60 100644 --- a/test/threadsafe_function/threadsafe_function_unref.cc +++ b/test/threadsafe_function/threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -11,30 +12,43 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); ThreadSafeFunction* tsfn = new ThreadSafeFunction; - *tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { - delete tsfn; - }); + *tsfn = ThreadSafeFunction::New( + info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { + delete tsfn; + }); tsfn->BlockingCall(); - setTimeout.Call( global, { - Function::New(env, [tsfn](const CallbackInfo& info) { - tsfn->Unref(info.Env()); - }), - Number::New(env, 100) - }); + setTimeout.Call( + global, + {Function::New( + env, [tsfn](const CallbackInfo& info) { tsfn->Unref(info.Env()); }), + Number::New(env, 100)}); return info.Env().Undefined(); } +static Value TestRef(const CallbackInfo& info) { + Function cb = info[1].As(); + + auto tsfn = ThreadSafeFunction::New(info.Env(), cb, "testRes", 1, 1); + + tsfn.BlockingCall(); + tsfn.Unref(info.Env()); + tsfn.Ref(info.Env()); + + return info.Env().Undefined(); } +} // namespace + Object InitThreadSafeFunctionUnref(Env env) { Object exports = Object::New(env); exports["testUnref"] = Function::New(env, TestUnref); + exports["testRef"] = Function::New(env, TestRef); return exports; } diff --git a/test/threadsafe_function/threadsafe_function_unref.js b/test/threadsafe_function/threadsafe_function_unref.js index e8f0ee391..1f0e96b87 100644 --- a/test/threadsafe_function/threadsafe_function_unref.js +++ b/test/threadsafe_function/threadsafe_function_unref.js @@ -1,9 +1,8 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -const isMainProcess = process.argv[1] != __filename; +const isMainProcess = process.argv[1] !== __filename; /** * In order to test that the event loop exits even with an active TSFN, we need @@ -12,44 +11,88 @@ const isMainProcess = process.argv[1] != __filename; * - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release. * * Main process should expect child process to exit. + * + * We also added a new test case for `Ref`. The idea being, if a TSFN is active, the event loop that it belongs to should not exit + * Our setup is similar to the test for the `Unref` case, with the difference being now we are expecting the child process to hang */ if (isMainProcess) { - module.exports = test(`../build/${buildType}/binding.node`) - .then(() => test(`../build/${buildType}/binding_noexcept.node`)); + module.exports = require('../common').runTestWithBindingPath(test); } else { - test(process.argv[2]); + const isTestingRef = (process.argv[3] === 'true'); + + if (isTestingRef) { + execTSFNRefTest(process.argv[2]); + } else { + execTSFNUnrefTest(process.argv[2]); + } } -function test(bindingFile) { - if (isMainProcess) { - // Main process +function testUnRefCallback (resolve, reject, bindingFile) { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile, false + ], { stdio: 'inherit' }); + + let timeout = setTimeout(function () { + child.kill(); + timeout = 0; + reject(new Error('Expected child to die')); + }, 5000); + + child.on('error', (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }); + + child.on('close', (code) => { + if (timeout) clearTimeout(timeout); + assert.strictEqual(code, 0, 'Expected return value 0'); + resolve(); + }); +} + +function testRefCallback (resolve, reject, bindingFile) { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile, true + ], { stdio: 'inherit' }); + + let timeout = setTimeout(function () { + child.kill(); + timeout = 0; + resolve(); + }, 1000); + + child.on('error', (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }); + + child.on('close', (code) => { + if (timeout) clearTimeout(timeout); + + reject(new Error('We expected Child to hang')); + }); +} + +function test (bindingFile) { + // Main process + return new Promise((resolve, reject) => { + testUnRefCallback(resolve, reject, bindingFile); + }).then(() => { return new Promise((resolve, reject) => { - const child = require('../napi_child').spawn(process.argv[0], [ - '--expose-gc', __filename, bindingFile - ], { stdio: 'inherit' }); - - let timeout = setTimeout( function() { - child.kill(); - timeout = 0; - reject(new Error("Expected child to die")); - }, 5000); - - child.on("error", (err) => { - clearTimeout(timeout); - timeout = 0; - reject(new Error(err)); - }) - - child.on("close", (code) => { - if (timeout) clearTimeout(timeout); - assert.strictEqual(code, 0, "Expected return value 0"); - resolve(); - }); + testRefCallback(resolve, reject, bindingFile); }); - } else { - // Child process - const binding = require(bindingFile); - binding.threadsafe_function_unref.testUnref({}, () => { }); - } + }); +} + +function execTSFNUnrefTest (bindingFile) { + const binding = require(bindingFile); + binding.threadsafe_function_unref.testUnref({}, () => { }); +} + +function execTSFNRefTest (bindingFile) { + const binding = require(bindingFile); + binding.threadsafe_function_unref.testRef({}, () => { }); } diff --git a/test/thunking_manual.cc b/test/thunking_manual.cc index d52302ea3..a9a0adf37 100644 --- a/test/thunking_manual.cc +++ b/test/thunking_manual.cc @@ -22,64 +22,59 @@ static Napi::Value TestGetter(const Napi::CallbackInfo& /*info*/) { return Napi::Value(); } -static void TestSetter(const Napi::CallbackInfo& /*info*/) { -} +static void TestSetter(const Napi::CallbackInfo& /*info*/) {} class TestClass : public Napi::ObjectWrap { public: - TestClass(const Napi::CallbackInfo& info): - ObjectWrap(info) { - } + TestClass(const Napi::CallbackInfo& info) : ObjectWrap(info) {} static Napi::Value TestClassStaticMethod(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), 42); } - static void TestClassStaticVoidMethod(const Napi::CallbackInfo& /*info*/) { - } + static void TestClassStaticVoidMethod(const Napi::CallbackInfo& /*info*/) {} Napi::Value TestClassInstanceMethod(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), 42); } - void TestClassInstanceVoidMethod(const Napi::CallbackInfo& /*info*/) { - } + void TestClassInstanceVoidMethod(const Napi::CallbackInfo& /*info*/) {} Napi::Value TestClassInstanceGetter(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), 42); } void TestClassInstanceSetter(const Napi::CallbackInfo& /*info*/, - const Napi::Value& /*new_value*/) { - } + const Napi::Value& /*new_value*/) {} static Napi::Function NewClass(Napi::Env env) { - return DefineClass(env, "TestClass", { - // Make sure to check that the deleter gets called. - StaticMethod("staticMethod", TestClassStaticMethod), - // Make sure to check that the deleter gets called. - StaticMethod("staticVoidMethod", TestClassStaticVoidMethod), - // Make sure to check that the deleter gets called. - StaticMethod(Napi::Symbol::New(env, "staticMethod"), - TestClassStaticMethod), - // Make sure to check that the deleter gets called. - StaticMethod(Napi::Symbol::New(env, "staticVoidMethod"), - TestClassStaticVoidMethod), - // Make sure to check that the deleter gets called. - InstanceMethod("instanceMethod", &TestClass::TestClassInstanceMethod), - // Make sure to check that the deleter gets called. - InstanceMethod("instanceVoidMethod", - &TestClass::TestClassInstanceVoidMethod), - // Make sure to check that the deleter gets called. - InstanceMethod(Napi::Symbol::New(env, "instanceMethod"), - &TestClass::TestClassInstanceMethod), - // Make sure to check that the deleter gets called. - InstanceMethod(Napi::Symbol::New(env, "instanceVoidMethod"), - &TestClass::TestClassInstanceVoidMethod), - // Make sure to check that the deleter gets called. - InstanceAccessor("instanceAccessor", - &TestClass::TestClassInstanceGetter, - &TestClass::TestClassInstanceSetter) - }); + return DefineClass( + env, + "TestClass", + {// Make sure to check that the deleter gets called. + StaticMethod("staticMethod", TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod("staticVoidMethod", TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticMethod"), + TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticVoidMethod"), + TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceMethod", &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceVoidMethod", + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceMethod"), + &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceVoidMethod"), + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceAccessor("instanceAccessor", + &TestClass::TestClassInstanceGetter, + &TestClass::TestClassInstanceSetter)}); } }; @@ -91,42 +86,34 @@ static Napi::Value CreateTestObject(const Napi::CallbackInfo& info) { item["testMethod"] = Napi::Function::New(env, TestMethod, "testMethod"); item.DefineProperties({ - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - "accessor_1", - TestGetter), - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - std::string("accessor_1_std_string"), - TestGetter), - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - Napi::String::New(info.Env(), - "accessor_1_js_string"), - TestGetter), - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - "accessor_2", - TestGetter, - TestSetter), - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - std::string("accessor_2_std_string"), - TestGetter, - TestSetter), - // Make sure to check that the deleter gets called. - Napi::PropertyDescriptor::Accessor(env, - item, - Napi::String::New(env, - "accessor_2_js_string"), - TestGetter, - TestSetter), - Napi::PropertyDescriptor::Value("TestClass", TestClass::NewClass(env)), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, item, "accessor_1", TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor( + env, item, std::string("accessor_1_std_string"), TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor( + env, + item, + Napi::String::New(info.Env(), "accessor_1_js_string"), + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor( + env, item, "accessor_2", TestGetter, TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + std::string("accessor_2_std_string"), + TestGetter, + TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor( + env, + item, + Napi::String::New(env, "accessor_2_js_string"), + TestGetter, + TestSetter), + Napi::PropertyDescriptor::Value("TestClass", TestClass::NewClass(env)), }); return item; diff --git a/test/thunking_manual.js b/test/thunking_manual.js index 22fb8877d..9ea72665f 100644 --- a/test/thunking_manual.js +++ b/test/thunking_manual.js @@ -1,18 +1,16 @@ // Flags: --expose-gc 'use strict'; -const buildType = 'Debug'; -const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { - console.log("Thunking: Performing initial GC"); +function test (binding) { + console.log('Thunking: Performing initial GC'); global.gc(); - console.log("Thunking: Creating test object"); + console.log('Thunking: Creating test object'); let object = binding.thunking_manual.createTestObject(); + // eslint-disable-next-line no-unused-vars object = null; - console.log("Thunking: About to GC\n--------"); + console.log('Thunking: About to GC\n--------'); global.gc(); - console.log("--------\nThunking: GC complete"); + console.log('--------\nThunking: GC complete'); } diff --git a/test/type_taggable.cc b/test/type_taggable.cc new file mode 100644 index 000000000..ac58f9281 --- /dev/null +++ b/test/type_taggable.cc @@ -0,0 +1,66 @@ +#include "napi.h" + +#if (NAPI_VERSION > 7) + +using namespace Napi; + +static const napi_type_tag type_tags[5] = { + {0xdaf987b3cc62481a, 0xb745b0497f299531}, + {0xbb7936c374084d9b, 0xa9548d0762eeedb9}, + {0xa5ed9ce2e4c00c38, 0}, + {0, 0}, + {0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a}, +}; + +template +class TestTypeTaggable { + public: + static Value TypeTaggedInstance(const CallbackInfo& info) { + TypeTaggable instance = Factory(info.Env()); + uint32_t type_index = info[0].As().Int32Value(); + + instance.TypeTag(&type_tags[type_index]); + + return instance; + } + + static Value CheckTypeTag(const CallbackInfo& info) { + uint32_t type_index = info[0].As().Int32Value(); + TypeTaggable instance = info[1].UnsafeAs(); + + return Boolean::New(info.Env(), + instance.CheckTypeTag(&type_tags[type_index])); + } +}; + +TypeTaggable ObjectFactory(Env env) { + return Object::New(env); +} + +TypeTaggable ExternalFactory(Env env) { + // External does not accept a nullptr for its data. + return External::New(env, reinterpret_cast(0x1)); +} + +using TestObject = TestTypeTaggable; +using TestExternal = TestTypeTaggable>; + +Object InitTypeTaggable(Env env) { + Object exports = Object::New(env); + + Object external = Object::New(env); + exports["external"] = external; + external["checkTypeTag"] = Function::New(env, &TestExternal::CheckTypeTag); + external["typeTaggedInstance"] = + Function::New(env, &TestExternal::TypeTaggedInstance); + + Object object = Object::New(env); + exports["object"] = object; + object["checkTypeTag"] = Function::New(env, &TestObject::CheckTypeTag); + object["typeTaggedInstance"] = + Function::New(env, &TestObject::TypeTaggedInstance); + + return exports; +} + +#endif diff --git a/test/type_taggable.js b/test/type_taggable.js new file mode 100644 index 000000000..7bc843cf6 --- /dev/null +++ b/test/type_taggable.js @@ -0,0 +1,59 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + +function testTypeTaggable ({ typeTaggedInstance, checkTypeTag }) { + const obj1 = typeTaggedInstance(0); + const obj2 = typeTaggedInstance(1); + + // Verify that type tags are correctly accepted. + assert.strictEqual(checkTypeTag(0, obj1), true); + assert.strictEqual(checkTypeTag(1, obj2), true); + + // Verify that wrongly tagged objects are rejected. + assert.strictEqual(checkTypeTag(0, obj2), false); + assert.strictEqual(checkTypeTag(1, obj1), false); + + // Verify that untagged objects are rejected. + assert.strictEqual(checkTypeTag(0, {}), false); + assert.strictEqual(checkTypeTag(1, {}), false); + + // Node v14 and v16 have an issue checking type tags if the `upper` in + // `napi_type_tag` is 0, so these tests can only be performed on Node version + // >=18. See: + // - https://github.com/nodejs/node/issues/43786 + // - https://github.com/nodejs/node/pull/43788 + const nodeVersion = parseInt(process.versions.node.split('.')[0]); + if (nodeVersion < 18) { + return; + } + + const obj3 = typeTaggedInstance(2); + const obj4 = typeTaggedInstance(3); + + // Verify that untagged objects are rejected. + assert.strictEqual(checkTypeTag(0, {}), false); + assert.strictEqual(checkTypeTag(1, {}), false); + + // Verify that type tags are correctly accepted. + assert.strictEqual(checkTypeTag(0, obj1), true); + assert.strictEqual(checkTypeTag(1, obj2), true); + assert.strictEqual(checkTypeTag(2, obj3), true); + assert.strictEqual(checkTypeTag(3, obj4), true); + + // Verify that wrongly tagged objects are rejected. + assert.strictEqual(checkTypeTag(0, obj2), false); + assert.strictEqual(checkTypeTag(1, obj1), false); + assert.strictEqual(checkTypeTag(0, obj3), false); + assert.strictEqual(checkTypeTag(1, obj4), false); + assert.strictEqual(checkTypeTag(2, obj4), false); + assert.strictEqual(checkTypeTag(3, obj3), false); + assert.strictEqual(checkTypeTag(4, obj3), false); +} + +function test (binding) { + testTypeTaggable(binding.type_taggable.external); + testTypeTaggable(binding.type_taggable.object); +} diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.cc b/test/typed_threadsafe_function/typed_threadsafe_function.cc index f9896db86..ce345b8f0 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function.cc @@ -1,4 +1,6 @@ #include +#include +#include #include #include "napi.h" @@ -17,6 +19,9 @@ static struct ThreadSafeFunctionInfo { bool startSecondary; FunctionReference jsFinalizeCallback; uint32_t maxQueueSize; + bool closeCalledFromJs; + std::mutex protect; + std::condition_variable signal; } tsfnInfo; static void TSFNCallJS(Env env, @@ -35,24 +40,25 @@ static void TSFNCallJS(Env env, } using TSFN = TypedThreadSafeFunction; -static TSFN tsfn; +static TSFN s_tsfn; // Thread data to transmit to JS static int ints[ARRAY_LENGTH]; static void SecondaryThread() { - if (tsfn.Release() != napi_ok) { - Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); + if (s_tsfn.Release() != napi_ok) { + Error::Fatal("TypedSecondaryThread", "ThreadSafeFunction.Release() failed"); } } // Source thread producing the data static void DataSourceThread() { - ThreadSafeFunctionInfo* info = tsfn.GetContext(); + ThreadSafeFunctionInfo* info = s_tsfn.GetContext(); if (info->startSecondary) { - if (tsfn.Acquire() != napi_ok) { - Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); + if (s_tsfn.Acquire() != napi_ok) { + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.Acquire() failed"); } threads[1] = std::thread(SecondaryThread); @@ -65,23 +71,23 @@ static void DataSourceThread() { switch (info->type) { case ThreadSafeFunctionInfo::DEFAULT: - status = tsfn.BlockingCall(); + status = s_tsfn.BlockingCall(); break; case ThreadSafeFunctionInfo::BLOCKING: - status = tsfn.BlockingCall(&ints[index]); + status = s_tsfn.BlockingCall(&ints[index]); break; case ThreadSafeFunctionInfo::NON_BLOCKING: - status = tsfn.NonBlockingCall(&ints[index]); + status = s_tsfn.NonBlockingCall(&ints[index]); break; } - if (info->maxQueueSize == 0) { - // Let's make this thread really busy for 200 ms to give the main thread a - // chance to abort. - auto start = std::chrono::high_resolution_clock::now(); - constexpr auto MS_200 = std::chrono::milliseconds(200); - for (; std::chrono::high_resolution_clock::now() - start < MS_200;) - ; + if (info->abort && info->type != ThreadSafeFunctionInfo::NON_BLOCKING) { + // Let's make this thread really busy to give the main thread a chance to + // abort / close. + std::unique_lock lk(info->protect); + while (!info->closeCalledFromJs) { + info->signal.wait(lk); + } } switch (status) { @@ -98,20 +104,22 @@ static void DataSourceThread() { break; default: - Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.*Call() failed"); } } if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) { - Error::Fatal("DataSourceThread", "Queue was never full"); + Error::Fatal("TypedDataSourceThread", "Queue was never full"); } if (info->abort && !queueWasClosing) { - Error::Fatal("DataSourceThread", "Queue was never closing"); + Error::Fatal("TypedDataSourceThread", "Queue was never closing"); } - if (!queueWasClosing && tsfn.Release() != napi_ok) { - Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); + if (!queueWasClosing && s_tsfn.Release() != napi_ok) { + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.Release() failed"); } } @@ -119,9 +127,14 @@ static Value StopThread(const CallbackInfo& info) { tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As()); bool abort = info[1].As(); if (abort) { - tsfn.Abort(); + s_tsfn.Abort(); } else { - tsfn.Release(); + s_tsfn.Release(); + } + { + std::lock_guard _(tsfnInfo.protect); + tsfnInfo.closeCalledFromJs = true; + tsfnInfo.signal.notify_one(); } return Value(); } @@ -145,16 +158,17 @@ static Value StartThreadInternal(const CallbackInfo& info, tsfnInfo.abort = info[1].As(); tsfnInfo.startSecondary = info[2].As(); tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); - - tsfn = TSFN::New(info.Env(), - info[0].As(), - Object::New(info.Env()), - "Test", - tsfnInfo.maxQueueSize, - 2, - &tsfnInfo, - JoinTheThreads, - threads); + tsfnInfo.closeCalledFromJs = false; + + s_tsfn = TSFN::New(info.Env(), + info[0].As(), + Object::New(info.Env()), + "Test", + tsfnInfo.maxQueueSize, + 2, + &tsfnInfo, + JoinTheThreads, + threads); threads[0] = std::thread(DataSourceThread); @@ -162,8 +176,8 @@ static Value StartThreadInternal(const CallbackInfo& info, } static Value Release(const CallbackInfo& /* info */) { - if (tsfn.Release() != napi_ok) { - Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); + if (s_tsfn.Release() != napi_ok) { + Error::Fatal("Release", "TypedThreadSafeFunction.Release() failed"); } return Value(); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.js b/test/typed_threadsafe_function/typed_threadsafe_function.js index 7aa8cc2ad..6ef2876d5 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function.js @@ -1,15 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = (async function () { - await test(require(`../build/${buildType}/binding.node`)); - await test(require(`../build/${buildType}/binding_noexcept.node`)); -})(); +module.exports = common.runTest(test); -async function test(binding) { +async function test (binding) { const expectedArray = (function (arrayLength) { const result = []; for (let index = 0; index < arrayLength; index++) { @@ -18,22 +14,21 @@ async function test(binding) { return result; })(binding.typed_threadsafe_function.ARRAY_LENGTH); - function testWithJSMarshaller({ + function testWithJSMarshaller ({ threadStarter, quitAfter, abort, maxQueueSize, - launchSecondary }) { + launchSecondary + }) { return new Promise((resolve) => { const array = []; - binding.typed_threadsafe_function[threadStarter](function testCallback(value) { + binding.typed_threadsafe_function[threadStarter](function testCallback (value) { array.push(value); if (array.length === quitAfter) { - setImmediate(() => { - binding.typed_threadsafe_function.stopThread(common.mustCall(() => { - resolve(array); - }), !!abort); - }); + binding.typed_threadsafe_function.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); } }, !!abort, !!launchSecondary, maxQueueSize); if (threadStarter === 'startThreadNonblocking') { @@ -45,9 +40,9 @@ async function test(binding) { }); } - await new Promise(function testWithoutJSMarshaller(resolve) { + await new Promise(function testWithoutJSMarshaller (resolve) { let callCount = 0; - binding.typed_threadsafe_function.startThreadNoNative(function testCallback() { + binding.typed_threadsafe_function.startThreadNoNative(function testCallback () { callCount++; // The default call-into-JS implementation passes no arguments. @@ -60,7 +55,7 @@ async function test(binding) { }); } }, false /* abort */, false /* launchSecondary */, - binding.typed_threadsafe_function.MAX_QUEUE_SIZE); + binding.typed_threadsafe_function.MAX_QUEUE_SIZE); }); // Start the thread in blocking mode, and assert that all values are passed. @@ -71,7 +66,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, quitAfter: binding.typed_threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -82,7 +77,7 @@ async function test(binding) { maxQueueSize: 0, quitAfter: binding.typed_threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in non-blocking mode, and assert that all values are @@ -93,7 +88,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, quitAfter: binding.typed_threadsafe_function.ARRAY_LENGTH }), - expectedArray, + expectedArray ); // Start the thread in blocking mode, and assert that all values are passed. @@ -104,7 +99,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, quitAfter: 1 }), - expectedArray, + expectedArray ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -115,10 +110,9 @@ async function test(binding) { maxQueueSize: 0, quitAfter: 1 }), - expectedArray, + expectedArray ); - // Start the thread in non-blocking mode, and assert that all values are // passed. Quit early, but let the thread finish. assert.deepStrictEqual( @@ -127,7 +121,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, quitAfter: 1 }), - expectedArray, + expectedArray ); // Start the thread in blocking mode, and assert that all values are passed. @@ -140,7 +134,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, launchSecondary: true }), - expectedArray, + expectedArray ); // Start the thread in non-blocking mode, and assert that all values are @@ -153,7 +147,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, launchSecondary: true }), - expectedArray, + expectedArray ); // Start the thread in blocking mode, and assert that it could not finish. @@ -165,7 +159,7 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, abort: true })).indexOf(0), - -1, + -1 ); // Start the thread in blocking mode with an infinite queue, and assert that @@ -177,7 +171,7 @@ async function test(binding) { maxQueueSize: 0, abort: true })).indexOf(0), - -1, + -1 ); // Start the thread in non-blocking mode, and assert that it could not finish. @@ -189,6 +183,6 @@ async function test(binding) { maxQueueSize: binding.typed_threadsafe_function.MAX_QUEUE_SIZE, abort: true })).indexOf(0), - -1, + -1 ); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc index ee70bb352..7cf2209dc 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc @@ -1,3 +1,4 @@ +#include #include "napi.h" #if (NAPI_VERSION > 3) @@ -11,7 +12,7 @@ namespace { class TSFNWrap : public ObjectWrap { public: - static Object Init(Napi::Env env, Object exports); + static Function Init(Napi::Env env); TSFNWrap(const CallbackInfo& info); Napi::Value GetContext(const CallbackInfo& /*info*/) { @@ -31,15 +32,14 @@ class TSFNWrap : public ObjectWrap { std::unique_ptr _deferred; }; -Object TSFNWrap::Init(Napi::Env env, Object exports) { +Function TSFNWrap::Init(Napi::Env env) { Function func = DefineClass(env, "TSFNWrap", {InstanceMethod("getContext", &TSFNWrap::GetContext), InstanceMethod("release", &TSFNWrap::Release)}); - exports.Set("TSFNWrap", func); - return exports; + return func; } TSFNWrap::TSFNWrap(const CallbackInfo& info) : ObjectWrap(info) { @@ -61,8 +61,60 @@ TSFNWrap::TSFNWrap(const CallbackInfo& info) : ObjectWrap(info) { } // namespace +struct SimpleTestContext { + SimpleTestContext(int val) : _val(val) {} + int _val = -1; +}; + +// A simple test to check that the context has been set successfully +void AssertGetContextFromTSFNNoFinalizerIsCorrect(const CallbackInfo& info) { + // Test the overload where we provide a resource name but no finalizer + using TSFN = TypedThreadSafeFunction; + SimpleTestContext* ctx = new SimpleTestContext(42); + TSFN tsfn = TSFN::New(info.Env(), "testRes", 1, 1, ctx); + + assert(tsfn.GetContext() == ctx); + delete ctx; + tsfn.Release(); + + // Test the other overload where we provide a async resource object, res name + // but no finalizer + ctx = new SimpleTestContext(52); + tsfn = TSFN::New( + info.Env(), Object::New(info.Env()), "testResourceObject", 1, 1, ctx); + + assert(tsfn.GetContext() == ctx); + delete ctx; + tsfn.Release(); + + ctx = new SimpleTestContext(52); + tsfn = TSFN::New(info.Env(), + "resStrings", + 1, + 1, + ctx, + [](Napi::Env, void*, SimpleTestContext*) {}); + + assert(tsfn.GetContext() == ctx); + delete ctx; + tsfn.Release(); + + ctx = new SimpleTestContext(52); + Function emptyFunc; + tsfn = TSFN::New(info.Env(), emptyFunc, "resString", 1, 1, ctx); + assert(tsfn.GetContext() == ctx); + delete ctx; + tsfn.Release(); +} + Object InitTypedThreadSafeFunctionCtx(Env env) { - return TSFNWrap::Init(env, Object::New(env)); + Object exports = Object::New(env); + Function tsfnWrap = TSFNWrap::Init(env); + + exports.Set("TSFNWrap", tsfnWrap); + exports.Set("AssertTSFNReturnCorrectCxt", + Function::New(env, AssertGetContextFromTSFNNoFinalizerIsCorrect)); + return exports; } #endif diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js index 2651586a0..ddbddccb9 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js @@ -1,14 +1,14 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); -async function test(binding) { +async function test (binding) { const ctx = { }; - const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); + const tsfn = new binding.typed_threadsafe_function_ctx.TSFNWrap(ctx); assert(tsfn.getContext() === ctx); await tsfn.release(); + + binding.typed_threadsafe_function_ctx.AssertTSFNReturnCorrectCxt(); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_exception.cc b/test/typed_threadsafe_function/typed_threadsafe_function_exception.cc new file mode 100644 index 000000000..c55ca23a4 --- /dev/null +++ b/test/typed_threadsafe_function/typed_threadsafe_function_exception.cc @@ -0,0 +1,39 @@ +#include +#include "napi.h" +#include "test_helper.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +void CallJS(Napi::Env env, + Napi::Function /* callback */, + std::nullptr_t* /* context */, + void* /*data*/) { + Napi::Error error = Napi::Error::New(env, "test-from-native"); + NAPI_THROW_VOID(error); +} + +using TSFN = TypedThreadSafeFunction; + +void TestCall(const CallbackInfo& info) { + Napi::Env env = info.Env(); + + TSFN wrapped = TSFN::New( + env, Napi::Function(), Object::New(env), String::New(env, "Test"), 0, 1); + wrapped.BlockingCall(static_cast(nullptr)); + wrapped.Release(); +} + +} // namespace + +Object InitTypedThreadSafeFunctionException(Env env) { + Object exports = Object::New(env); + exports["testCall"] = Function::New(env, TestCall); + + return exports; +} + +#endif diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_exception.js b/test/typed_threadsafe_function/typed_threadsafe_function_exception.js new file mode 100644 index 000000000..60ff67363 --- /dev/null +++ b/test/typed_threadsafe_function/typed_threadsafe_function_exception.js @@ -0,0 +1,13 @@ +'use strict'; + +const common = require('../common'); + +module.exports = common.runTest(test); + +async function test () { + await common.runTestInChildProcess({ + suite: 'typed_threadsafe_function_exception', + testName: 'testCall', + execArgv: ['--force-node-api-uncaught-exceptions-policy=true'] + }); +} diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc index eccf87c93..daa273fcb 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ #include #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -64,11 +65,13 @@ static Value TestCall(const CallbackInfo& info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js index b6df669d4..17185a86b 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js @@ -2,16 +2,13 @@ const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; +module.exports = require('../common').runTest(test); -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); - -async function test(binding) { +async function test (binding) { const testCall = binding.typed_threadsafe_function_existing_tsfn.testCall; - assert.strictEqual(typeof await testCall({ blocking: true, data: true }), "number"); - assert.strictEqual(typeof await testCall({ blocking: true, data: false }), "undefined"); - assert.strictEqual(typeof await testCall({ blocking: false, data: true }), "number"); - assert.strictEqual(typeof await testCall({ blocking: false, data: false }), "undefined"); + assert.strictEqual(typeof await testCall({ blocking: true, data: true }), 'number'); + assert.strictEqual(typeof await testCall({ blocking: true, data: false }), 'undefined'); + assert.strictEqual(typeof await testCall({ blocking: false, data: true }), 'number'); + assert.strictEqual(typeof await testCall({ blocking: false, data: false }), 'undefined'); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc index 891fd560c..a4da743e1 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc @@ -16,12 +16,16 @@ static Value Test(const CallbackInfo& info) { return info.Env().Undefined(); } +static Value ExtractEnvNullValue(const CallbackInfo& info) { + return info.Env().Null(); +} + } // namespace Object InitTypedThreadSafeFunctionPtr(Env env) { Object exports = Object::New(env); exports["test"] = Function::New(env, Test); - + exports["null"] = Function::New(env, ExtractEnvNullValue); return exports; } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js index 47b187761..e91921755 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js @@ -1,10 +1,8 @@ 'use strict'; +const assert = require('assert'); +module.exports = require('../common').runTest(test); -const buildType = process.config.target_defaults.default_configuration; - -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - -function test(binding) { - binding.typed_threadsafe_function_ptr.test({}, () => {}); +function test (binding) { + assert(binding.typed_threadsafe_function_ptr.test({}, () => {}) === undefined); + assert(binding.typed_threadsafe_function_ptr.null() === null); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_sum.cc b/test/typed_threadsafe_function/typed_threadsafe_function_sum.cc index 9add259c4..b36ab2837 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_sum.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_sum.cc @@ -99,7 +99,7 @@ class DelayedTSFNTask { // Entry point for std::thread void entryDelayedTSFN(int threadId) { std::unique_lock lk(mtx); - cv.wait(lk); + cv.wait(lk, [this] { return this->tsfn != nullptr; }); tsfn.BlockingCall(new double(threadId)); tsfn.Release(); }; diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_sum.js b/test/typed_threadsafe_function/typed_threadsafe_function_sum.js index 8f10476f6..0a59bba72 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_sum.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_sum.js @@ -1,6 +1,5 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; /** * @@ -29,21 +28,20 @@ const buildType = process.config.target_defaults.default_configuration; const THREAD_COUNT = 5; const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); /** @param {number[]} N */ const sum = (N) => N.reduce((sum, n) => sum + n, 0); -function test(binding) { - async function check(bindingFunction) { +function test (binding) { + async function check (bindingFunction) { const calls = []; const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); assert.ok(result); assert.equal(sum(calls), EXPECTED_SUM); } - async function checkAcquire() { + async function checkAcquire () { const calls = []; const { promise, createThread, stopThreads } = binding.typed_threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); for (let i = 0; i < THREAD_COUNT; i++) { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc index 35345568d..b6588e29f 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -14,7 +15,7 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); TSFN* tsfn = new TSFN; *tsfn = TSFN::New( @@ -31,6 +32,7 @@ static Value TestUnref(const CallbackInfo& info) { static_cast(nullptr)); tsfn->BlockingCall(); + tsfn->Ref(info.Env()); setTimeout.Call( global, @@ -41,11 +43,24 @@ static Value TestUnref(const CallbackInfo& info) { return info.Env().Undefined(); } +static Value TestRef(const CallbackInfo& info) { + Function cb = info[1].As(); + + auto tsfn = TSFN::New(info.Env(), cb, "testRes", 1, 1, nullptr); + + tsfn.BlockingCall(); + tsfn.Unref(info.Env()); + tsfn.Ref(info.Env()); + + return info.Env().Undefined(); +} + } // namespace Object InitTypedThreadSafeFunctionUnref(Env env) { Object exports = Object::New(env); exports["testUnref"] = Function::New(env, TestUnref); + exports["testRef"] = Function::New(env, TestRef); return exports; } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_unref.js b/test/typed_threadsafe_function/typed_threadsafe_function_unref.js index 55b42a553..88bf7d340 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_unref.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_unref.js @@ -1,9 +1,8 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -const isMainProcess = process.argv[1] != __filename; +const isMainProcess = process.argv[1] !== __filename; /** * In order to test that the event loop exits even with an active TSFN, we need @@ -12,44 +11,88 @@ const isMainProcess = process.argv[1] != __filename; * - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release. * * Main process should expect child process to exit. + * + * We also added a new test case for `Ref`. The idea being, if a TSFN is active, the event loop that it belongs to should not exit + * Our setup is similar to the test for the `Unref` case, with the difference being now we are expecting the child process to hang */ if (isMainProcess) { - module.exports = test(`../build/${buildType}/binding.node`) - .then(() => test(`../build/${buildType}/binding_noexcept.node`)); + module.exports = require('../common').runTestWithBindingPath(test); } else { - test(process.argv[2]); + const isTestingRef = (process.argv[3] === 'true'); + + if (isTestingRef) { + execTSFNRefTest(process.argv[2]); + } else { + execTSFNUnrefTest(process.argv[2]); + } } -function test(bindingFile) { - if (isMainProcess) { - // Main process +function testUnRefCallback (resolve, reject, bindingFile) { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile, false + ], { stdio: 'inherit' }); + + let timeout = setTimeout(function () { + child.kill(); + timeout = 0; + reject(new Error('Expected child to die')); + }, 5000); + + child.on('error', (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }); + + child.on('close', (code) => { + if (timeout) clearTimeout(timeout); + assert.strictEqual(code, 0, 'Expected return value 0'); + resolve(); + }); +} + +function testRefCallback (resolve, reject, bindingFile) { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile, true + ], { stdio: 'inherit' }); + + let timeout = setTimeout(function () { + child.kill(); + timeout = 0; + resolve(); + }, 1000); + + child.on('error', (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }); + + child.on('close', (code) => { + if (timeout) clearTimeout(timeout); + + reject(new Error('We expected Child to hang')); + }); +} + +function test (bindingFile) { + // Main process + return new Promise((resolve, reject) => { + testUnRefCallback(resolve, reject, bindingFile); + }).then(() => { return new Promise((resolve, reject) => { - const child = require('../napi_child').spawn(process.argv[0], [ - '--expose-gc', __filename, bindingFile - ], { stdio: 'inherit' }); - - let timeout = setTimeout( function() { - child.kill(); - timeout = 0; - reject(new Error("Expected child to die")); - }, 5000); - - child.on("error", (err) => { - clearTimeout(timeout); - timeout = 0; - reject(new Error(err)); - }) - - child.on("close", (code) => { - if (timeout) clearTimeout(timeout); - assert.strictEqual(code, 0, "Expected return value 0"); - resolve(); - }); + testRefCallback(resolve, reject, bindingFile); }); - } else { - // Child process - const binding = require(bindingFile); - binding.typed_threadsafe_function_unref.testUnref({}, () => { }); - } + }); +} + +function execTSFNUnrefTest (bindingFile) { + const binding = require(bindingFile); + binding.typed_threadsafe_function_unref.testUnref({}, () => { }); +} + +function execTSFNRefTest (bindingFile) { + const binding = require(bindingFile); + binding.typed_threadsafe_function_unref.testRef({}, () => { }); } diff --git a/test/typedarray-bigint.js b/test/typedarray-bigint.js index ce66d3898..1c3174525 100644 --- a/test/typedarray-bigint.js +++ b/test/typedarray-bigint.js @@ -1,14 +1,13 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { [ ['bigint64', BigInt64Array], - ['biguint64', BigUint64Array], + ['biguint64', BigUint64Array] ].forEach(([type, Constructor]) => { try { const length = 4; diff --git a/test/typedarray.cc b/test/typedarray.cc index 08c667699..795f2819e 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -1,14 +1,18 @@ +#include #include "napi.h" - using namespace Napi; #if defined(NAPI_HAS_CONSTEXPR) -#define NAPI_TYPEDARRAY_NEW(className, env, length, type) className::New(env, length) -#define NAPI_TYPEDARRAY_NEW_BUFFER(className, env, length, buffer, bufferOffset, type) \ +#define NAPI_TYPEDARRAY_NEW(className, env, length, type) \ + className::New(env, length) +#define NAPI_TYPEDARRAY_NEW_BUFFER( \ + className, env, length, buffer, bufferOffset, type) \ className::New(env, length, buffer, bufferOffset) #else -#define NAPI_TYPEDARRAY_NEW(className, env, length, type) className::New(env, length, type) -#define NAPI_TYPEDARRAY_NEW_BUFFER(className, env, length, buffer, bufferOffset, type) \ +#define NAPI_TYPEDARRAY_NEW(className, env, length, type) \ + className::New(env, length, type) +#define NAPI_TYPEDARRAY_NEW_BUFFER( \ + className, env, length, buffer, bufferOffset, type) \ className::New(env, length, buffer, bufferOffset, type) #endif @@ -17,92 +21,254 @@ namespace { Value CreateTypedArray(const CallbackInfo& info) { std::string arrayType = info[0].As(); size_t length = info[1].As().Uint32Value(); - ArrayBuffer buffer = info[2].As(); - size_t bufferOffset = info[3].IsUndefined() ? 0 : info[3].As().Uint32Value(); + Value buffer = info[2]; + size_t bufferOffset = + info[3].IsUndefined() ? 0 : info[3].As().Uint32Value(); if (arrayType == "int8") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Int8Array, info.Env(), length, napi_int8_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, info.Env(), length, buffer, bufferOffset, - napi_int8_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Int8Array, info.Env(), length, napi_int8_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_int8_array); } else if (arrayType == "uint8") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Uint8Array, info.Env(), length, napi_uint8_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Uint8Array, info.Env(), length, buffer, bufferOffset, - napi_uint8_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Uint8Array, info.Env(), length, napi_uint8_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Uint8Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_uint8_array); } else if (arrayType == "uint8_clamped") { - return buffer.IsUndefined() ? - Uint8Array::New(info.Env(), length, napi_uint8_clamped_array) : - Uint8Array::New(info.Env(), length, buffer, bufferOffset, napi_uint8_clamped_array); + return buffer.IsUndefined() + ? Uint8Array::New(info.Env(), length, napi_uint8_clamped_array) + : Uint8Array::New(info.Env(), + length, + buffer.As(), + bufferOffset, + napi_uint8_clamped_array); } else if (arrayType == "int16") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Int16Array, info.Env(), length, napi_int16_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Int16Array, info.Env(), length, buffer, bufferOffset, - napi_int16_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Int16Array, info.Env(), length, napi_int16_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Int16Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_int16_array); } else if (arrayType == "uint16") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Uint16Array, info.Env(), length, napi_uint16_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Uint16Array, info.Env(), length, buffer, bufferOffset, - napi_uint16_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Uint16Array, info.Env(), length, napi_uint16_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Uint16Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_uint16_array); } else if (arrayType == "int32") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Int32Array, info.Env(), length, napi_int32_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Int32Array, info.Env(), length, buffer, bufferOffset, - napi_int32_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Int32Array, info.Env(), length, napi_int32_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Int32Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_int32_array); } else if (arrayType == "uint32") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Uint32Array, info.Env(), length, napi_uint32_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Uint32Array, info.Env(), length, buffer, bufferOffset, - napi_uint32_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Uint32Array, info.Env(), length, napi_uint32_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Uint32Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_uint32_array); } else if (arrayType == "float32") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Float32Array, info.Env(), length, napi_float32_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Float32Array, info.Env(), length, buffer, bufferOffset, - napi_float32_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Float32Array, info.Env(), length, napi_float32_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Float32Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_float32_array); } else if (arrayType == "float64") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, - napi_float64_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + Float64Array, info.Env(), length, napi_float64_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_float64_array); #if (NAPI_VERSION > 5) } else if (arrayType == "bigint64") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(BigInt64Array, info.Env(), length, buffer, bufferOffset, - napi_bigint64_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + BigInt64Array, info.Env(), length, napi_bigint64_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(BigInt64Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_bigint64_array); } else if (arrayType == "biguint64") { - return buffer.IsUndefined() ? - NAPI_TYPEDARRAY_NEW(BigUint64Array, info.Env(), length, napi_biguint64_array) : - NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, info.Env(), length, buffer, bufferOffset, - napi_biguint64_array); + return buffer.IsUndefined() + ? NAPI_TYPEDARRAY_NEW( + BigUint64Array, info.Env(), length, napi_biguint64_array) + : NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, + info.Env(), + length, + buffer.As(), + bufferOffset, + napi_biguint64_array); #endif } else { - Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Invalid typed-array type.") + .ThrowAsJavaScriptException(); return Value(); } } Value CreateInvalidTypedArray(const CallbackInfo& info) { - return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, info.Env(), 1, ArrayBuffer(), 0, napi_int8_array); + return NAPI_TYPEDARRAY_NEW_BUFFER( + Int8Array, info.Env(), 1, ArrayBuffer(), 0, napi_int8_array); } Value GetTypedArrayType(const CallbackInfo& info) { TypedArray array = info[0].As(); switch (array.TypedArrayType()) { - case napi_int8_array: return String::New(info.Env(), "int8"); - case napi_uint8_array: return String::New(info.Env(), "uint8"); - case napi_uint8_clamped_array: return String::New(info.Env(), "uint8_clamped"); - case napi_int16_array: return String::New(info.Env(), "int16"); - case napi_uint16_array: return String::New(info.Env(), "uint16"); - case napi_int32_array: return String::New(info.Env(), "int32"); - case napi_uint32_array: return String::New(info.Env(), "uint32"); - case napi_float32_array: return String::New(info.Env(), "float32"); - case napi_float64_array: return String::New(info.Env(), "float64"); + case napi_int8_array: + return String::New(info.Env(), "int8"); + case napi_uint8_array: + return String::New(info.Env(), "uint8"); + case napi_uint8_clamped_array: + return String::New(info.Env(), "uint8_clamped"); + case napi_int16_array: + return String::New(info.Env(), "int16"); + case napi_uint16_array: + return String::New(info.Env(), "uint16"); + case napi_int32_array: + return String::New(info.Env(), "int32"); + case napi_uint32_array: + return String::New(info.Env(), "uint32"); + case napi_float32_array: + return String::New(info.Env(), "float32"); + case napi_float64_array: + return String::New(info.Env(), "float64"); +#if (NAPI_VERSION > 5) + case napi_bigint64_array: + return String::New(info.Env(), "bigint64"); + case napi_biguint64_array: + return String::New(info.Env(), "biguint64"); +#endif + default: + return String::New(info.Env(), "invalid"); + } +} + +template +bool TypedArrayDataIsEquivalent(TypedArrayOf arr, + TypedArrayOf inputArr) { + if (arr.ElementLength() != inputArr.ElementLength()) { + return false; + } + std::vector bufferContent(arr.Data(), arr.Data() + arr.ElementLength()); + std::vector inputContent(inputArr.Data(), + inputArr.Data() + inputArr.ElementLength()); + if (bufferContent != inputContent) { + return false; + } + return true; +} + +Value CheckBufferContent(const CallbackInfo& info) { + TypedArray array = info[0].As(); + + switch (array.TypedArrayType()) { + case napi_int8_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + break; + case napi_uint8_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_uint8_clamped_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_int16_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_uint16_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_int32_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_uint32_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_float32_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_float64_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + #if (NAPI_VERSION > 5) - case napi_bigint64_array: return String::New(info.Env(), "bigint64"); - case napi_biguint64_array: return String::New(info.Env(), "biguint64"); + case napi_bigint64_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + + case napi_biguint64_array: + return Boolean::New( + info.Env(), + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); + #endif - default: return String::New(info.Env(), "invalid"); + default: + return Boolean::New(info.Env(), false); } } @@ -111,11 +277,31 @@ Value GetTypedArrayLength(const CallbackInfo& info) { return Number::New(info.Env(), static_cast(array.ElementLength())); } +Value GetTypedArraySize(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return Number::New(info.Env(), static_cast(array.ElementSize())); +} + +Value GetTypedArrayByteOffset(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return Number::New(info.Env(), static_cast(array.ByteOffset())); +} + +Value GetTypedArrayByteLength(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return Number::New(info.Env(), static_cast(array.ByteLength())); +} + Value GetTypedArrayBuffer(const CallbackInfo& info) { TypedArray array = info[0].As(); return array.ArrayBuffer(); } +Value GetTypedArrayBufferValue(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return array.Buffer(); +} + Value GetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); @@ -145,7 +331,8 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return BigInt::New(info.Env(), array.As()[index]); #endif default: - Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Invalid typed-array type.") + .ThrowAsJavaScriptException(); return Value(); } } @@ -153,64 +340,99 @@ Value GetTypedArrayElement(const CallbackInfo& info) { void SetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); - Number value = info[2].As(); + Value value = info[2]; switch (array.TypedArrayType()) { case napi_int8_array: - array.As()[index] = static_cast(value.Int32Value()); + array.As()[index] = + static_cast(value.As().Int32Value()); break; case napi_uint8_array: - array.As()[index] = static_cast(value.Uint32Value()); + array.As()[index] = + static_cast(value.As().Uint32Value()); break; case napi_uint8_clamped_array: - array.As()[index] = static_cast(value.Uint32Value()); + array.As()[index] = + static_cast(value.As().Uint32Value()); break; case napi_int16_array: - array.As()[index] = static_cast(value.Int32Value()); + array.As()[index] = + static_cast(value.As().Int32Value()); break; case napi_uint16_array: - array.As()[index] = static_cast(value.Uint32Value()); + array.As()[index] = + static_cast(value.As().Uint32Value()); break; case napi_int32_array: - array.As()[index] = value.Int32Value(); + array.As()[index] = value.As().Int32Value(); break; case napi_uint32_array: - array.As()[index] = value.Uint32Value(); + array.As()[index] = value.As().Uint32Value(); break; case napi_float32_array: - array.As()[index] = value.FloatValue(); + array.As()[index] = value.As().FloatValue(); break; case napi_float64_array: - array.As()[index] = value.DoubleValue(); + array.As()[index] = value.As().DoubleValue(); break; #if (NAPI_VERSION > 5) case napi_bigint64_array: { bool lossless; - array.As()[index] = value.As().Int64Value(&lossless); + array.As()[index] = + value.As().Int64Value(&lossless); break; } case napi_biguint64_array: { bool lossless; - array.As()[index] = value.As().Uint64Value(&lossless); + array.As()[index] = + value.As().Uint64Value(&lossless); break; } #endif default: - Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Invalid typed-array type.") + .ThrowAsJavaScriptException(); } } -} // end anonymous namespace +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value CreateInt8TypedArrayFromSharedArrayBuffer(const CallbackInfo& info) { + auto buffer = info[0].As(); + size_t length = buffer.ByteLength(); + + return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, + info.Env(), + length, + buffer.As(), + 0, + napi_int8_array); +} +#endif + +} // end anonymous namespace Object InitTypedArray(Env env) { Object exports = Object::New(env); exports["createTypedArray"] = Function::New(env, CreateTypedArray); - exports["createInvalidTypedArray"] = Function::New(env, CreateInvalidTypedArray); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createInt8TypedArrayFromSharedArrayBuffer"] = + Function::New(env, CreateInt8TypedArrayFromSharedArrayBuffer); +#endif + exports["createInvalidTypedArray"] = + Function::New(env, CreateInvalidTypedArray); exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType); exports["getTypedArrayLength"] = Function::New(env, GetTypedArrayLength); + exports["getTypedArraySize"] = Function::New(env, GetTypedArraySize); + exports["getTypedArrayByteOffset"] = + Function::New(env, GetTypedArrayByteOffset); + exports["getTypedArrayByteLength"] = + Function::New(env, GetTypedArrayByteLength); exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer); + exports["getTypedArrayBufferValue"] = + Function::New(env, GetTypedArrayBufferValue); exports["getTypedArrayElement"] = Function::New(env, GetTypedArrayElement); exports["setTypedArrayElement"] = Function::New(env, SetTypedArrayElement); + exports["checkBufferContent"] = Function::New(env, CheckBufferContent); return exports; } diff --git a/test/typedarray.js b/test/typedarray.js index 9aa880c16..b6ae4a7f7 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -1,23 +1,49 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +let runSharedArrayBufferTests = true; + +module.exports = require('./common').runTest(test); -function test(binding) { +function test (binding) { const testData = [ - [ 'int8', Int8Array ], - [ 'uint8', Uint8Array ], - [ 'uint8_clamped', Uint8ClampedArray ], - [ 'int16', Int16Array ], - [ 'uint16', Uint16Array ], - [ 'int32', Int32Array ], - [ 'uint32', Uint32Array ], - [ 'float32', Float32Array ], - [ 'float64', Float64Array ], + ['int8', Int8Array, 1, new Int8Array([0, 124, 24, 44])], + ['uint8', Uint8Array, 1, new Uint8Array([0, 255, 2, 14])], + ['uint8_clamped', Uint8ClampedArray, 1, new Uint8ClampedArray([0, 256, 0, 255])], + ['int16', Int16Array, 2, new Int16Array([-32768, 32767, 1234, 42])], + ['uint16', Uint16Array, 2, new Uint16Array([0, 65535, 4, 12])], + ['int32', Int32Array, 4, new Int32Array([Math.pow(2, 31), Math.pow(-2, 31), 255, 4])], + ['uint32', Uint32Array, 4, new Uint32Array([0, Math.pow(2, 32), 24, 125])], + ['float32', Float32Array, 4, new Float32Array([0, 21, 34, 45])], + ['float64', Float64Array, 8, new Float64Array([0, 4124, 45, 90])] + ]; + + const bigIntTests = [ + ['bigint64', BigInt64Array, 8, new BigInt64Array([9007199254740991n, 9007199254740991n, 24n, 125n])], + ['biguint64', BigUint64Array, 8, new BigUint64Array([9007199254740991n, 9007199254740991n, 2345n, 345n])] ]; + bigIntTests.forEach(data => { + const length = 4; + const t = binding.typedarray.createTypedArray(data[0], length); + assert.ok(t instanceof data[1]); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), data[0]); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + assert.strictEqual(binding.typedarray.getTypedArraySize(t), data[2]); + assert.strictEqual(binding.typedarray.getTypedArrayByteOffset(t), 0); + assert.strictEqual(binding.typedarray.getTypedArrayByteLength(t), data[2] * length); + + t[3] = 11n; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); + binding.typedarray.setTypedArrayElement(t, 3, 22n); + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); + assert.strictEqual(t[3], 22n); + + const nonEmptyTypedArray = binding.typedarray.createTypedArray(data[0], length, data[3].buffer); + binding.typedarray.checkBufferContent(nonEmptyTypedArray, data[3]); + }); + testData.forEach(data => { try { const length = 4; @@ -25,6 +51,9 @@ function test(binding) { assert.ok(t instanceof data[1]); assert.strictEqual(binding.typedarray.getTypedArrayType(t), data[0]); assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + assert.strictEqual(binding.typedarray.getTypedArraySize(t), data[2]); + assert.strictEqual(binding.typedarray.getTypedArrayByteOffset(t), 0); + assert.strictEqual(binding.typedarray.getTypedArrayByteLength(t), data[2] * length); t[3] = 11; assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11); @@ -34,6 +63,9 @@ function test(binding) { const b = binding.typedarray.getTypedArrayBuffer(t); assert.ok(b instanceof ArrayBuffer); + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof ArrayBuffer); + assert.strictEqual(b, bAsValue); } catch (e) { console.log(data); throw e; @@ -50,6 +82,9 @@ function test(binding) { assert.ok(t instanceof data[1]); assert.strictEqual(binding.typedarray.getTypedArrayType(t), data[0]); assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + assert.strictEqual(binding.typedarray.getTypedArraySize(t), data[2]); + assert.strictEqual(binding.typedarray.getTypedArrayByteOffset(t), offset); + assert.strictEqual(binding.typedarray.getTypedArrayByteLength(t), data[2] * length); t[3] = 11; assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11); @@ -58,6 +93,9 @@ function test(binding) { assert.strictEqual(t[3], 22); assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); + + const nonEmptyTypedArray = binding.typedarray.createTypedArray(data[0], length, data[3].buffer); + assert.strictEqual(binding.typedarray.checkBufferContent(nonEmptyTypedArray, data[3]), true); } catch (e) { console.log(data); throw e; @@ -67,4 +105,35 @@ function test(binding) { assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); + + if (binding.hasSharedArrayBuffer && runSharedArrayBufferTests) { + const length = 4; + const sab = new SharedArrayBuffer(length); + /** @type {Int8Array} */ + let t; + + try { + t = binding.typedarray.createInt8TypedArrayFromSharedArrayBuffer(sab); + } catch (ex) { + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating TypedArrays on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } + + throw ex; + } + + assert.ok(t instanceof Int8Array); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), 'int8'); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + for (let i = 0; i < length; i++) { + const value = 2 ** (i + 1); + t[i] = value; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, i), value); + } + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof SharedArrayBuffer); + assert.strictEqual(bAsValue, sab); + } } diff --git a/test/value_type_cast.cc b/test/value_type_cast.cc new file mode 100644 index 000000000..dfc03b38b --- /dev/null +++ b/test/value_type_cast.cc @@ -0,0 +1,70 @@ +#include "common/test_helper.h" +#include "napi.h" + +using namespace Napi; + +#define TYPE_CAST_TYPES(V) \ + V(Boolean) \ + V(Number) \ + V(BigInt) \ + V(Date) \ + V(String) \ + V(Symbol) \ + V(Object) \ + V(Array) \ + V(ArrayBuffer) \ + V(TypedArray) \ + V(DataView) \ + V(Function) \ + V(Promise) + +// The following types are tested individually. +// External +// TypedArrayOf +// Buffer + +namespace { +#define V(Type) \ + void TypeCast##Type(const CallbackInfo& info) { USE(info[0].As()); } +TYPE_CAST_TYPES(V) + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +V(SharedArrayBuffer) +#endif + +#undef V + +void TypeCastBuffer(const CallbackInfo& info) { + USE(info[0].As>()); +} + +void TypeCastExternal(const CallbackInfo& info) { + USE(info[0].As>()); +} + +void TypeCastTypeArrayOfUint8(const CallbackInfo& info) { + USE(info[0].As>()); +} +} // namespace + +Object InitValueTypeCast(Env env, Object exports) { + exports["external"] = External::New(env, nullptr); + +#define V(Type) exports["typeCast" #Type] = Function::New(env, TypeCast##Type); + TYPE_CAST_TYPES(V) + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + V(SharedArrayBuffer) +#endif + +#undef V + + exports["typeCastBuffer"] = Function::New(env, TypeCastBuffer); + exports["typeCastExternal"] = Function::New(env, TypeCastExternal); + exports["typeCastTypeArrayOfUint8"] = + Function::New(env, TypeCastTypeArrayOfUint8); + + return exports; +} + +NODE_API_MODULE(addon, InitValueTypeCast) diff --git a/test/value_type_cast.js b/test/value_type_cast.js new file mode 100644 index 000000000..274dd278b --- /dev/null +++ b/test/value_type_cast.js @@ -0,0 +1,113 @@ +'use strict'; + +const assert = require('assert'); +const napiChild = require('./napi_child'); + +module.exports = require('./common').runTestWithBuildType(test); + +function test (buildType) { + const binding = require(`./build/${buildType}/binding_type_check.node`); + const testTable = { + typeCastBoolean: { + positiveValues: [true, false], + negativeValues: [{}, [], 1, 1n, 'true', null, undefined] + }, + typeCastNumber: { + positiveValues: [1, NaN], + negativeValues: [{}, [], true, 1n, '1', null, undefined] + }, + typeCastBigInt: { + positiveValues: [1n], + negativeValues: [{}, [], true, 1, '1', null, undefined] + }, + typeCastDate: { + positiveValues: [new Date()], + negativeValues: [{}, [], true, 1, 1n, '1', null, undefined] + }, + typeCastString: { + positiveValues: ['', '1'], + negativeValues: [{}, [], true, 1, 1n, null, undefined] + }, + typeCastSymbol: { + positiveValues: [Symbol('1')], + negativeValues: [{}, [], true, 1, 1n, '1', null, undefined] + }, + typeCastObject: { + positiveValues: [{}, new Date(), []], + negativeValues: [true, 1, 1n, '1', null, undefined] + }, + typeCastArray: { + positiveValues: [[1]], + negativeValues: [{}, true, 1, 1n, '1', null, undefined] + }, + typeCastArrayBuffer: { + positiveValues: [new ArrayBuffer(0)], + negativeValues: [new Uint8Array(1), new SharedArrayBuffer(0), {}, [], null, undefined] + }, + typeCastTypedArray: { + positiveValues: [new Uint8Array(0)], + negativeValues: [new ArrayBuffer(1), {}, [], null, undefined] + }, + typeCastDataView: { + positiveValues: [new DataView(new ArrayBuffer(0))], + negativeValues: [new ArrayBuffer(1), null, undefined] + }, + typeCastFunction: { + positiveValues: [() => {}], + negativeValues: [{}, null, undefined] + }, + typeCastPromise: { + positiveValues: [Promise.resolve()], + // napi_is_promise distinguishes Promise and thenable. + negativeValues: [{ then: () => {} }, null, undefined] + }, + typeCastBuffer: { + positiveValues: [Buffer.from('')], + // napi_is_buffer doesn't distinguish between Buffer and TypedArrays. + negativeValues: [new ArrayBuffer(1), null, undefined] + }, + typeCastExternal: { + positiveValues: [binding.external], + negativeValues: [{}, null, undefined] + }, + typeCastTypeArrayOfUint8: { + // TypedArrayOf::CheckCast doesn't distinguish between Uint8ClampedArray and Uint8Array. + positiveValues: [new Uint8Array(0), new Uint8ClampedArray(0)], + negativeValues: [new Int8Array(1), null, undefined] + } + }; + + if ('typeCastSharedArrayBuffer' in binding) { + testTable.typeCastSharedArrayBuffer = { + positiveValues: [new SharedArrayBuffer(0)], + negativeValues: [new Uint8Array(1), new ArrayBuffer(0), {}, [], null, undefined] + }; + } + + if (process.argv[2] === 'child') { + child(binding, testTable, process.argv[3], process.argv[4], parseInt(process.argv[5])); + return; + } + + for (const [methodName, { positiveValues, negativeValues }] of Object.entries(testTable)) { + for (const idx of positiveValues.keys()) { + const { status } = napiChild.spawnSync(process.execPath, [__filename, 'child', methodName, 'positiveValues', idx]); + assert.strictEqual(status, 0, `${methodName} positive value ${idx} test failed`); + } + for (const idx of negativeValues.keys()) { + const { status, signal, stderr } = napiChild.spawnSync(process.execPath, [__filename, 'child', methodName, 'negativeValues', idx], { + encoding: 'utf8' + }); + if (process.platform === 'win32') { + assert.strictEqual(status, 128 + 6 /* SIGABRT */, `${methodName} negative value ${idx} test failed`); + } else { + assert.strictEqual(signal, 'SIGABRT', `${methodName} negative value ${idx} test failed`); + } + assert.ok(stderr.match(/FATAL ERROR: .*::CheckCast.*/)); + } + } +} + +async function child (binding, testTable, methodName, type, idx) { + binding[methodName](testTable[methodName][type][idx]); +} diff --git a/test/version_management.cc b/test/version_management.cc index 39dfeecf4..6496f5700 100644 --- a/test/version_management.cc +++ b/test/version_management.cc @@ -3,25 +3,26 @@ using namespace Napi; Value getNapiVersion(const CallbackInfo& info) { - Napi::Env env = info.Env(); - uint32_t napi_version = VersionManagement::GetNapiVersion(env); - return Number::New(env, napi_version); + Napi::Env env = info.Env(); + uint32_t napi_version = VersionManagement::GetNapiVersion(env); + return Number::New(env, napi_version); } Value getNodeVersion(const CallbackInfo& info) { - Napi::Env env = info.Env(); - const napi_node_version* node_version = VersionManagement::GetNodeVersion(env); - Object version = Object::New(env); - version.Set("major", Number::New(env, node_version->major)); - version.Set("minor", Number::New(env, node_version->minor)); - version.Set("patch", Number::New(env, node_version->patch)); - version.Set("release", String::New(env, node_version->release)); - return version; + Napi::Env env = info.Env(); + const napi_node_version* node_version = + VersionManagement::GetNodeVersion(env); + Object version = Object::New(env); + version.Set("major", Number::New(env, node_version->major)); + version.Set("minor", Number::New(env, node_version->minor)); + version.Set("patch", Number::New(env, node_version->patch)); + version.Set("release", String::New(env, node_version->release)); + return version; } Object InitVersionManagement(Env env) { - Object exports = Object::New(env); - exports["getNapiVersion"] = Function::New(env, getNapiVersion); - exports["getNodeVersion"] = Function::New(env, getNodeVersion); - return exports; + Object exports = Object::New(env); + exports["getNapiVersion"] = Function::New(env, getNapiVersion); + exports["getNodeVersion"] = Function::New(env, getNodeVersion); + return exports; } diff --git a/test/version_management.js b/test/version_management.js index f52db2f73..06d4f9c94 100644 --- a/test/version_management.js +++ b/test/version_management.js @@ -1,32 +1,29 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function parseVersion() { - const expected = {}; - expected.napi = parseInt(process.versions.napi); - expected.release = process.release.name; - const nodeVersion = process.versions.node.split('.'); - expected.major = parseInt(nodeVersion[0]); - expected.minor = parseInt(nodeVersion[1]); - expected.patch = parseInt(nodeVersion[2]); - return expected; +function parseVersion () { + const expected = {}; + expected.napi = parseInt(process.versions.napi); + expected.release = process.release.name; + const nodeVersion = process.versions.node.split('.'); + expected.major = parseInt(nodeVersion[0]); + expected.minor = parseInt(nodeVersion[1]); + expected.patch = parseInt(nodeVersion[2]); + return expected; } -function test(binding) { - - const expected = parseVersion(); - - const napiVersion = binding.version_management.getNapiVersion(); - assert.strictEqual(napiVersion, expected.napi); +function test (binding) { + const expected = parseVersion(); - const nodeVersion = binding.version_management.getNodeVersion(); - assert.strictEqual(nodeVersion.major, expected.major); - assert.strictEqual(nodeVersion.minor, expected.minor); - assert.strictEqual(nodeVersion.patch, expected.patch); - assert.strictEqual(nodeVersion.release, expected.release); + const napiVersion = binding.version_management.getNapiVersion(); + assert.strictEqual(napiVersion, expected.napi); + const nodeVersion = binding.version_management.getNodeVersion(); + assert.strictEqual(nodeVersion.major, expected.major); + assert.strictEqual(nodeVersion.minor, expected.minor); + assert.strictEqual(nodeVersion.patch, expected.patch); + assert.strictEqual(nodeVersion.release, expected.release); } diff --git a/tools/check-napi.js b/tools/check-napi.js index 48fdfc077..9199af334 100644 --- a/tools/check-napi.js +++ b/tools/check-napi.js @@ -4,16 +4,15 @@ const fs = require('fs'); const path = require('path'); -const child_process = require('child_process'); // Read the output of the command, break it into lines, and use the reducer to // decide whether the file is an N-API module or not. -function checkFile(file, command, argv, reducer) { - const child = child_process.spawn(command, argv, { +function checkFile (file, command, argv, reducer) { + const child = require('child_process').spawn(command, argv, { stdio: ['inherit', 'pipe', 'inherit'] }); let leftover = ''; - let isNapi = undefined; + let isNapi; child.stdout.on('data', (chunk) => { if (isNapi === undefined) { chunk = (leftover + chunk.toString()).split(/[\r\n]+/); @@ -27,11 +26,11 @@ function checkFile(file, command, argv, reducer) { child.on('close', (code, signal) => { if ((code === null && signal !== null) || (code !== 0)) { console.log( - command + ' exited with code: ' + code + ' and signal: ' + signal); + command + ' exited with code: ' + code + ' and signal: ' + signal); } else { // Green if it's a N-API module, red otherwise. console.log( - '\x1b[' + (isNapi ? '42' : '41') + 'm' + + '\x1b[' + (isNapi ? '42' : '41') + 'm' + (isNapi ? ' N-API' : 'Not N-API') + '\x1b[0m: ' + file); } @@ -39,7 +38,7 @@ function checkFile(file, command, argv, reducer) { } // Use nm -a to list symbols. -function checkFileUNIX(file) { +function checkFileUNIX (file) { checkFile(file, 'nm', ['-a', file], (soFar, line) => { if (soFar === undefined) { line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/); @@ -54,7 +53,7 @@ function checkFileUNIX(file) { } // Use dumpbin /imports to list symbols. -function checkFileWin32(file) { +function checkFileWin32 (file) { checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => { if (soFar === undefined) { line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/); @@ -68,16 +67,16 @@ function checkFileWin32(file) { // Descend into a directory structure and pass each file ending in '.node' to // one of the above checks, depending on the OS. -function recurse(top) { +function recurse (top) { fs.readdir(top, (error, items) => { if (error) { - throw ("error reading directory " + top + ": " + error); + throw new Error('error reading directory ' + top + ': ' + error); } items.forEach((item) => { item = path.join(top, item); fs.stat(item, ((item) => (error, stats) => { if (error) { - throw ("error about " + item + ": " + error); + throw new Error('error about ' + item + ': ' + error); } if (stats.isDirectory()) { recurse(item); @@ -86,9 +85,9 @@ function recurse(top) { // artefacts of node-addon-api having identified a version of // Node.js that ships with a correct implementation of N-API. path.basename(item) !== 'nothing.node') { - process.platform === 'win32' ? - checkFileWin32(item) : - checkFileUNIX(item); + process.platform === 'win32' + ? checkFileWin32(item) + : checkFileUNIX(item); } })(item)); }); diff --git a/tools/clang-format.js b/tools/clang-format.js index 026728b5c..e4bb4f52e 100644 --- a/tools/clang-format.js +++ b/tools/clang-format.js @@ -4,40 +4,64 @@ const spawn = require('child_process').spawnSync; const path = require('path'); const filesToCheck = ['*.h', '*.cc']; -const CLANG_FORMAT_START = process.env.CLANG_FORMAT_START || 'master'; - -function main(args) { - let clangFormatPath = path.dirname(require.resolve('clang-format')); - const options = ['--binary=node_modules/.bin/clang-format', '--style=file']; - - const gitClangFormatPath = path.join(clangFormatPath, - 'bin/git-clang-format'); - const result = spawn('python', [ - gitClangFormatPath, - ...options, - '--diff', - CLANG_FORMAT_START, - 'HEAD', - ...filesToCheck - ], { encoding: 'utf-8' }); - - if (result.error) { - console.error('Error running git-clang-format:', result.error); +const FORMAT_START = process.env.FORMAT_START || 'main'; + +function main (args) { + let fix = false; + while (args.length > 0) { + switch (args[0]) { + case '-f': + case '--fix': + fix = true; + break; + default: + } + args.shift(); + } + + const clangFormatPath = path.dirname(require.resolve('clang-format')); + const binary = process.platform === 'win32' + ? 'node_modules\\.bin\\clang-format.cmd' + : 'node_modules/.bin/clang-format'; + const options = ['--binary=' + binary, '--style=file']; + if (fix) { + options.push(FORMAT_START); + } else { + options.push('--diff', FORMAT_START); + } + + const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); + const result = spawn( + 'python', + [gitClangFormatPath, ...options, '--', ...filesToCheck], + { encoding: 'utf-8' } + ); + + if (result.stderr) { + console.error('Error running git-clang-format:', result.stderr); return 2; } const clangFormatOutput = result.stdout.trim(); - if (clangFormatOutput !== '' && - clangFormatOutput !== ('no modified files to format') && - clangFormatOutput !== ('clang-format did not modify any files')) { + // Bail fast if in fix mode. + if (fix) { + console.log(clangFormatOutput); + return 0; + } + // Detect if there is any complains from clang-format + if ( + clangFormatOutput !== '' && + clangFormatOutput !== 'no modified files to format' && + clangFormatOutput !== 'clang-format did not modify any files' + ) { console.error(clangFormatOutput); - const fixCmd = '"npm run lint:fix"'; + const fixCmd = 'npm run lint:fix'; console.error(` - ERROR: please run ${fixCmd} to format changes in your commit + ERROR: please run "${fixCmd}" to format changes in your commit Note that when running the command locally, please keep your local - master branch and working branch up to date with nodejs/node-addon-api + main branch and working branch up to date with nodejs/node-addon-api to exclude un-related complains. - Or you can run "env CLANG_FORMAT_START=upstream/master ${fixCmd}".`); + Or you can run "env FORMAT_START=upstream/main ${fixCmd}".`); return 1; } } diff --git a/tools/conversion.js b/tools/conversion.js index 5aef2c3ff..e92a03a26 100755 --- a/tools/conversion.js +++ b/tools/conversion.js @@ -1,6 +1,6 @@ #! /usr/bin/env node -'use strict' +'use strict'; const fs = require('fs'); const path = require('path'); @@ -12,258 +12,250 @@ if (!dir) { process.exit(1); } -const NodeApiVersion = require('../package.json').version; +const NodeApiVersion = require('../').version; const disable = args[1]; -if (disable != "--disable" && dir != "--disable") { - var ConfigFileOperations = { +let ConfigFileOperations; +if (disable !== '--disable' && dir !== '--disable') { + ConfigFileOperations = { 'package.json': [ - [ /([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'], - [ /[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, '' ] + [/([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'], + [/[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, ''] ], 'binding.gyp': [ - [ /([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \'\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {' ], - [ /Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);\s+(\w+)\.Reset\((\1)\);\s+\1->SetClassName\((Nan::String::New|Nan::New<(v8::)*String>)\("(.+?)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$6", {'], - [ /Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {' ], - [ /Nan::New\(([\w\d:]+)\)->GetFunction\(\)/g, 'Napi::Function::New(env, $1)' ], - [ /Nan::New\(([\w\d:]+)\)->GetFunction()/g, 'Napi::Function::New(env, $1);' ], - [ /Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)' ], - [ /Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)' ], + [/v8::Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], + [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);\s+(\w+)\.Reset\((\1)\);\s+\1->SetClassName\((Nan::String::New|Nan::New<(v8::)*String>)\("(.+?)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$6", {'], + [/Local\s+(\w+)\s*=\s*Nan::New\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'], + [/Nan::New\(([\w\d:]+)\)->GetFunction\(\)/g, 'Napi::Function::New(env, $1)'], + [/Nan::New\(([\w\d:]+)\)->GetFunction()/g, 'Napi::Function::New(env, $1);'], + [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], + [/Nan::New\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'], // FunctionTemplate to FunctionReference - [ /Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference' ], - [ /Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference' ], - [ /v8::Local/g, 'Napi::FunctionReference' ], - [ /Local/g, 'Napi::FunctionReference' ], - [ /v8::FunctionTemplate/g, 'Napi::FunctionReference' ], - [ /FunctionTemplate/g, 'Napi::FunctionReference' ], - - - [ /([ ]*)Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, '$1InstanceMethod("$2", &$3),' ], - [ /([ ]*)(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm, + [/Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'], + [/Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'], + [/v8::Local/g, 'Napi::FunctionReference'], + [/Local/g, 'Napi::FunctionReference'], + [/v8::FunctionTemplate/g, 'Napi::FunctionReference'], + [/FunctionTemplate/g, 'Napi::FunctionReference'], + + [/([ ]*)Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, '$1InstanceMethod("$2", &$3),'], + [/([ ]*)(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm, '});\n\n' + '$1constructor = Napi::Persistent($3);\n' + '$1constructor.SuppressDestruct();\n' + - '$1target.Set("$2", $3);' ], - + '$1target.Set("$2", $3);'], // TODO: Other attribute combinations - [ /static_cast\(ReadOnly\s*\|\s*DontDelete\)/gm, - 'static_cast(napi_enumerable | napi_configurable)' ], + [/static_cast\(ReadOnly\s*\|\s*DontDelete\)/gm, + 'static_cast(napi_enumerable | napi_configurable)'], - [ /([\w\d:<>]+?)::Cast\((.+?)\)/g, '$2.As<$1>()' ], + [/([\w\d:<>]+?)::Cast\((.+?)\)/g, '$2.As<$1>()'], - [ /\*Nan::Utf8String\(([^)]+)\)/g, '$1->As().Utf8Value().c_str()' ], - [ /Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As()' ], - [ /Nan::Utf8String/g, 'std::string' ], + [/\*Nan::Utf8String\(([^)]+)\)/g, '$1->As().Utf8Value().c_str()'], + [/Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As()'], + [/Nan::Utf8String/g, 'std::string'], - [ /v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)' ], - [ /String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)' ], - [ /\.length\(\)/g, '.Length()' ], + [/v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], + [/String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'], + [/\.length\(\)/g, '.Length()'], - [ /Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,' ], + [/Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,'], - [ /class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>' ], - [ /(\w+)\(([^\)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3' ], + [/class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>'], + [/(\w+)\(([^)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3'], // HandleOKCallback to OnOK - [ /HandleOKCallback/g, 'OnOK' ], + [/HandleOKCallback/g, 'OnOK'], // HandleErrorCallback to OnError - [ /HandleErrorCallback/g, 'OnError' ], + [/HandleErrorCallback/g, 'OnError'], // ex. .As() to .As() - [ /\.As\(\)/g, '.As()' ], - [ /\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As()' ], + [/\.As\(\)/g, '.As()'], + [/\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As()'], // ex. Nan::New(info[0]) to Napi::Number::New(info[0]) - [ /Nan::New<(v8::)*Integer>\((.+?)\)/g, 'Napi::Number::New(env, $2)' ], - [ /Nan::New\(([0-9\.]+)\)/g, 'Napi::Number::New(env, $1)' ], - [ /Nan::New<(v8::)*String>\("(.+?)"\)/g, 'Napi::String::New(env, "$2")' ], - [ /Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")' ], - [ /Nan::New<(v8::)*(.+?)>\(\)/g, 'Napi::$2::New(env)' ], - [ /Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)' ], - [ /Nan::New<(v8::)*(.+?)>\(/g, 'Napi::$2::New(env, ' ], - [ /Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, ' ], - [ /Nan::NewBuffer\(/g, 'Napi::Buffer::New(env, ' ], + [/Nan::New<(v8::)*Integer>\((.+?)\)/g, 'Napi::Number::New(env, $2)'], + [/Nan::New\(([0-9.]+)\)/g, 'Napi::Number::New(env, $1)'], + [/Nan::New<(v8::)*String>\("(.+?)"\)/g, 'Napi::String::New(env, "$2")'], + [/Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")'], + [/Nan::New<(v8::)*(.+?)>\(\)/g, 'Napi::$2::New(env)'], + [/Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)'], + [/Nan::New<(v8::)*(.+?)>\(/g, 'Napi::$2::New(env, '], + [/Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, '], + [/Nan::NewBuffer\(/g, 'Napi::Buffer::New(env, '], // TODO: Properly handle this - [ /Nan::New\(/g, 'Napi::New(env, ' ], + [/Nan::New\(/g, 'Napi::New(env, '], - [ /\.IsInt32\(\)/g, '.IsNumber()' ], - [ /->IsInt32\(\)/g, '.IsNumber()' ], + [/\.IsInt32\(\)/g, '.IsNumber()'], + [/->IsInt32\(\)/g, '.IsNumber()'], - - [ /(.+?)->BooleanValue\(\)/g, '$1.As().Value()' ], - [ /(.+?)->Int32Value\(\)/g, '$1.As().Int32Value()' ], - [ /(.+?)->Uint32Value\(\)/g, '$1.As().Uint32Value()' ], - [ /(.+?)->IntegerValue\(\)/g, '$1.As().Int64Value()' ], - [ /(.+?)->NumberValue\(\)/g, '$1.As().DoubleValue()' ], + [/(.+?)->BooleanValue\(\)/g, '$1.As().Value()'], + [/(.+?)->Int32Value\(\)/g, '$1.As().Int32Value()'], + [/(.+?)->Uint32Value\(\)/g, '$1.As().Uint32Value()'], + [/(.+?)->IntegerValue\(\)/g, '$1.As().Int64Value()'], + [/(.+?)->NumberValue\(\)/g, '$1.As().DoubleValue()'], // ex. Nan::To(info[0]) to info[0].Value() - [ /Nan::To\((.+?)\)/g, '$2.To()' ], - [ /Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To()' ], + [/Nan::To\((.+?)\)/g, '$2.To()'], + [/Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To()'], // ex. Nan::To(info[0]) to info[0].As().Value() - [ /Nan::To\((.+?)\)/g, '$1.As().Value()' ], + [/Nan::To\((.+?)\)/g, '$1.As().Value()'], // ex. Nan::To(info[0]) to info[0].As().Int32Value() - [ /Nan::To\((.+?)\)/g, '$1.As().Int32Value()' ], + [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], // ex. Nan::To(info[0]) to info[0].As().Int32Value() - [ /Nan::To\((.+?)\)/g, '$1.As().Int32Value()' ], + [/Nan::To\((.+?)\)/g, '$1.As().Int32Value()'], // ex. Nan::To(info[0]) to info[0].As().Uint32Value() - [ /Nan::To\((.+?)\)/g, '$1.As().Uint32Value()' ], + [/Nan::To\((.+?)\)/g, '$1.As().Uint32Value()'], // ex. Nan::To(info[0]) to info[0].As().Int64Value() - [ /Nan::To\((.+?)\)/g, '$1.As().Int64Value()' ], + [/Nan::To\((.+?)\)/g, '$1.As().Int64Value()'], // ex. Nan::To(info[0]) to info[0].As().FloatValue() - [ /Nan::To\((.+?)\)/g, '$1.As().FloatValue()' ], + [/Nan::To\((.+?)\)/g, '$1.As().FloatValue()'], // ex. Nan::To(info[0]) to info[0].As().DoubleValue() - [ /Nan::To\((.+?)\)/g, '$1.As().DoubleValue()' ], - - [ /Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())' ], + [/Nan::To\((.+?)\)/g, '$1.As().DoubleValue()'], - [ /Nan::Has\(([^,]+),\s*/gm, '($1).Has(' ], - [ /\.Has\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Has($1)' ], - [ /\.Has\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Has($1)' ], + [/Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())'], - [ /Nan::Get\(([^,]+),\s*/gm, '($1).Get(' ], - [ /\.Get\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Get($1)' ], - [ /\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)' ], + [/Nan::Has\(([^,]+),\s*/gm, '($1).Has('], + [/\.Has\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Has($1)'], + [/\.Has\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Has($1)'], - [ /Nan::Set\(([^,]+),\s*/gm, '($1).Set(' ], - [ /\.Set\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\s*,/gm, '.Set($1,' ], - [ /\.Set\([\s|\\]*Nan::New\(([^)]+)\)\s*,/gm, '.Set($1,' ], + [/Nan::Get\(([^,]+),\s*/gm, '($1).Get('], + [/\.Get\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Get($1)'], + [/\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)'], + [/Nan::Set\(([^,]+),\s*/gm, '($1).Set('], + [/\.Set\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\s*,/gm, '.Set($1,'], + [/\.Set\([\s|\\]*Nan::New\(([^)]+)\)\s*,/gm, '.Set($1,'], // ex. node::Buffer::HasInstance(info[0]) to info[0].IsBuffer() - [ /node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()' ], + [/node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()'], // ex. node::Buffer::Length(info[0]) to info[0].Length() - [ /node::Buffer::Length\((.+?)\)/g, '$1.As>().Length()' ], + [/node::Buffer::Length\((.+?)\)/g, '$1.As>().Length()'], // ex. node::Buffer::Data(info[0]) to info[0].Data() - [ /node::Buffer::Data\((.+?)\)/g, '$1.As>().Data()' ], - [ /Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, ' ], + [/node::Buffer::Data\((.+?)\)/g, '$1.As>().Data()'], + [/Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, '], // Nan::AsyncQueueWorker(worker) - [ /Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();' ], - [ /Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()' ], + [/Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();'], + [/Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()'], // Nan::ThrowError(error) to Napi::Error::New(env, error).ThrowAsJavaScriptException() - [ /([ ]*)return Nan::Throw(\w*?)Error\((.+?)\);/g, '$1Napi::$2Error::New(env, $3).ThrowAsJavaScriptException();\n$1return env.Null();' ], - [ /Nan::Throw(\w*?)Error\((.+?)\);\n(\s*)return;/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n$3return env.Null();' ], - [ /Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n' ], + [/([ ]*)return Nan::Throw(\w*?)Error\((.+?)\);/g, '$1Napi::$2Error::New(env, $3).ThrowAsJavaScriptException();\n$1return env.Null();'], + [/Nan::Throw(\w*?)Error\((.+?)\);\n(\s*)return;/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n$3return env.Null();'], + [/Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n'], // Nan::RangeError(error) to Napi::RangeError::New(env, error) - [ /Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)' ], + [/Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)'], - [ /Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)' ], + [/Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)'], - [ /Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);' ], - [ /Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope' ], - [ /Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty(' ], - [ /\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", ' ], + [/Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);'], + [/Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope'], + [/Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty('], + [/\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", '], // [ /Nan::GetPropertyNames\(([^,]+)\)/, '$1->GetPropertyNames()' ], - [ /Nan::Equals\(([^,]+),/g, '$1.StrictEquals(' ], - - - [ /(.+)->Set\(/g, '$1.Set\(' ], - - - [ /Nan::Callback/g, 'Napi::FunctionReference' ], - + [/Nan::Equals\(([^,]+),/g, '$1.StrictEquals('], - [ /Nan::Persistent/g, 'Napi::ObjectReference' ], - [ /Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target' ], + [/(.+)->Set\(/g, '$1.Set('], - [ /(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;' ], - [ /Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();' ], + [/Nan::Callback/g, 'Napi::FunctionReference'], - [ /Nan::NAN_METHOD_RETURN_TYPE/g, 'void' ], - [ /NAN_INLINE/g, 'inline' ], + [/Nan::Persistent/g, 'Napi::ObjectReference'], + [/Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target'], - [ /Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&' ], - [ /NAN_METHOD\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], - [ /static\s*NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)' ], - [ /NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)' ], - [ /static\s*NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)' ], - [ /NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)' ], - [ /void Init\((v8::)*Local<(v8::)*Object> exports\)/g, 'Napi::Object Init(Napi::Env env, Napi::Object exports)' ], - [ /NAN_MODULE_INIT\(([\w\d:]+?)\);/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports);' ], - [ /NAN_MODULE_INIT\(([\w\d:]+?)\)/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports)' ], + [/(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;'], + [/Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();'], + [/Nan::NAN_METHOD_RETURN_TYPE/g, 'void'], + [/NAN_INLINE/g, 'inline'], - [ /::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)' ], - [ /constructor_template/g, 'constructor' ], + [/Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&'], + [/NAN_METHOD\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/static\s*NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'], + [/static\s*NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], + [/NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'], + [/void Init\((v8::)*Local<(v8::)*Object> exports\)/g, 'Napi::Object Init(Napi::Env env, Napi::Object exports)'], + [/NAN_MODULE_INIT\(([\w\d:]+?)\);/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports);'], + [/NAN_MODULE_INIT\(([\w\d:]+?)\)/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports)'], - [ /Nan::FunctionCallbackInfo<(v8::)?Value>[ ]*& [ ]*info\)[ ]*{\n*([ ]*)/gm, 'Napi::CallbackInfo& info) {\n$2Napi::Env env = info.Env();\n$2' ], - [ /Nan::FunctionCallbackInfo<(v8::)*Value>\s*&\s*info\);/g, 'Napi::CallbackInfo& info);' ], - [ /Nan::FunctionCallbackInfo<(v8::)*Value>\s*&/g, 'Napi::CallbackInfo&' ], + [/::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)'], + [/constructor_template/g, 'constructor'], - [ /Buffer::HasInstance\(([^)]+)\)/g, '$1.IsBuffer()' ], + [/Nan::FunctionCallbackInfo<(v8::)?Value>[ ]*& [ ]*info\)[ ]*{\n*([ ]*)/gm, 'Napi::CallbackInfo& info) {\n$2Napi::Env env = info.Env();\n$2'], + [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&\s*info\);/g, 'Napi::CallbackInfo& info);'], + [/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&/g, 'Napi::CallbackInfo&'], - [ /info\[(\d+)\]->/g, 'info[$1].' ], - [ /info\[([\w\d]+)\]->/g, 'info[$1].' ], - [ /info\.This\(\)->/g, 'info.This().' ], - [ /->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()' ], - [ /info.GetReturnValue\(\).SetUndefined\(\)/g, 'return env.Undefined()' ], - [ /info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;' ], + [/Buffer::HasInstance\(([^)]+)\)/g, '$1.IsBuffer()'], + [/info\[(\d+)\]->/g, 'info[$1].'], + [/info\[([\w\d]+)\]->/g, 'info[$1].'], + [/info\.This\(\)->/g, 'info.This().'], + [/->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()'], + [/info.GetReturnValue\(\).SetUndefined\(\)/g, 'return env.Undefined()'], + [/info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;'], // ex. Local to Napi::Value - [ /v8::Local/g, 'Napi::$1' ], - [ /Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1' ], + [/v8::Local/g, 'Napi::$1'], + [/Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'], // Declare an env in helper functions that take a Napi::Value - [ /(\w+)\(Napi::Value (\w+)(,\s*[^\()]+)?\)\s*{\n*([ ]*)/gm, '$1(Napi::Value $2$3) {\n$4Napi::Env env = $2.Env();\n$4' ], + [/(\w+)\(Napi::Value (\w+)(,\s*[^()]+)?\)\s*{\n*([ ]*)/gm, '$1(Napi::Value $2$3) {\n$4Napi::Env env = $2.Env();\n$4'], // delete #include and/or - [ /#include +(<|")(?:node|nan).h("|>)/g, "#include $1napi.h$2\n#include $1uv.h$2" ], + [/#include +(<|")(?:node|nan).h("|>)/g, '#include $1napi.h$2\n#include $1uv.h$2'], // NODE_MODULE to NODE_API_MODULE - [ /NODE_MODULE/g, 'NODE_API_MODULE' ], - [ /Nan::/g, 'Napi::' ], - [ /nan.h/g, 'napi.h' ], + [/NODE_MODULE/g, 'NODE_API_MODULE'], + [/Nan::/g, 'Napi::'], + [/nan.h/g, 'napi.h'], // delete .FromJust() - [ /\.FromJust\(\)/g, '' ], + [/\.FromJust\(\)/g, ''], // delete .ToLocalCheck() - [ /\.ToLocalChecked\(\)/g, '' ], - [ /^.*->SetInternalFieldCount\(.*$/gm, '' ], + [/\.ToLocalChecked\(\)/g, ''], + [/^.*->SetInternalFieldCount\(.*$/gm, ''], // replace using node; and/or using v8; to using Napi; - [ /using (node|v8);/g, 'using Napi;' ], - [ /using namespace (node|Nan|v8);/g, 'using namespace Napi;' ], + [/using (node|v8);/g, 'using Napi;'], + [/using namespace (node|Nan|v8);/g, 'using namespace Napi;'], // delete using v8::Local; - [ /using v8::Local;\n/g, '' ], + [/using v8::Local;\n/g, ''], // replace using v8::XXX; with using Napi::XXX - [ /using v8::([A-Za-z]+);/g, 'using Napi::$1;' ], + [/using v8::([A-Za-z]+);/g, 'using Napi::$1;'] ]; -var paths = listFiles(dir); -paths.forEach(function(dirEntry) { - var filename = dirEntry.split('\\').pop().split('/').pop(); +const paths = listFiles(dir); +paths.forEach(function (dirEntry) { + const filename = dirEntry.split('\\').pop().split('/').pop(); // Check whether the file is a source file or a config file // then execute function accordingly - var sourcePattern = /.+\.h|.+\.cc|.+\.cpp/; + const sourcePattern = /.+\.h|.+\.cc|.+\.cpp/; if (sourcePattern.test(filename)) { convertFile(dirEntry, SourceFileOperations); } else if (ConfigFileOperations[filename] != null) { @@ -271,12 +263,12 @@ paths.forEach(function(dirEntry) { } }); -function listFiles(dir, filelist) { - var files = fs.readdirSync(dir); +function listFiles (dir, filelist) { + const files = fs.readdirSync(dir); filelist = filelist || []; - files.forEach(function(file) { + files.forEach(function (file) { if (file === 'node_modules') { - return + return; } if (fs.statSync(path.join(dir, file)).isDirectory()) { @@ -288,21 +280,21 @@ function listFiles(dir, filelist) { return filelist; } -function convert(content, operations) { - for (let i = 0; i < operations.length; i ++) { - let operation = operations[i]; +function convert (content, operations) { + for (let i = 0; i < operations.length; i++) { + const operation = operations[i]; content = content.replace(operation[0], operation[1]); } return content; } -function convertFile(fileName, operations) { - fs.readFile(fileName, "utf-8", function (err, file) { +function convertFile (fileName, operations) { + fs.readFile(fileName, 'utf-8', function (err, file) { if (err) throw err; file = convert(file, operations); - fs.writeFile(fileName, file, function(err){ + fs.writeFile(fileName, file, function (err) { if (err) throw err; }); }); diff --git a/unit-test/.gitignore b/unit-test/.gitignore new file mode 100644 index 000000000..40a7bd0a1 --- /dev/null +++ b/unit-test/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/build +/generated diff --git a/unit-test/README.md b/unit-test/README.md new file mode 100644 index 000000000..2dfd5abfb --- /dev/null +++ b/unit-test/README.md @@ -0,0 +1,38 @@ + +# Enable running tests with specific filter conditions: + +The `--filter` option limits which test modules are executed by `node test`. +The default `pretest` step is still `node-gyp rebuild -C test`, so +`npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. + +### Example: + + - perform the default test rebuild, then run only the `objectwrap` + test module +``` + npm test --filter=objectwrap +``` + + +# Wildcards are also possible: + +### Example: + + - perform the default test rebuild, then run all test modules ending + with `reference` + (`function_reference`, `object_reference`, and `reference`) +``` + npm test --filter=*reference +``` + +# Multiple filter conditions are also allowed + +### Example: + + - perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module +``` + npm test --filter='*function objectwrap' +``` diff --git a/unit-test/binding-file-template.js b/unit-test/binding-file-template.js new file mode 100644 index 000000000..2c7b3aa8b --- /dev/null +++ b/unit-test/binding-file-template.js @@ -0,0 +1,39 @@ +const path = require('path'); +const fs = require('fs'); + +/** + * @param bindingConfigurations + * This method acts as a template to generate the content of binding.cc file + */ +module.exports.generateFileContent = function (bindingConfigurations) { + const content = []; + const inits = []; + const exports = []; + + for (const config of bindingConfigurations) { + inits.push(`Object Init${config.objectName}(Env env);`); + exports.push(`exports.Set("${config.propertyName}", Init${config.objectName}(env));`); + } + + content.push('#include "napi.h"'); + content.push('using namespace Napi;'); + + inits.forEach(init => content.push(init)); + + content.push('Object Init(Env env, Object exports) {'); + + exports.forEach(exp => content.push(exp)); + + content.push('return exports;'); + content.push('}'); + content.push('NODE_API_MODULE(addon, Init);'); + + return Promise.resolve(content.join('\r\n')); +}; + +module.exports.writeToBindingFile = function writeToBindingFile (content) { + const generatedFilePath = path.join(__dirname, 'generated', 'binding.cc'); + fs.writeFileSync(generatedFilePath, ''); + fs.writeFileSync(generatedFilePath, content, { flag: 'a' }); + console.log('generated binding file ', generatedFilePath, new Date()); +}; diff --git a/unit-test/binding.gyp b/unit-test/binding.gyp new file mode 100644 index 000000000..f701c9296 --- /dev/null +++ b/unit-test/binding.gyp @@ -0,0 +1,72 @@ +{ + 'target_defaults': { + 'includes': ['../common.gypi'], + 'include_dirs': ['../test/common', "./generated"], + 'variables': { + 'setup': ["@(build_sources)'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_noexcept', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, + { + 'target_name': 'binding_swallowexcept', + 'includes': ['../except.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_swallowexcept_noexcept', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_custom_namespace', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NAPI_CPP_CUSTOM_NAMESPACE=cstm'], + 'dependencies': [ 'generateBindingCC' ] + }, + ], +} diff --git a/unit-test/exceptions.js b/unit-test/exceptions.js new file mode 100644 index 000000000..bf7ed32db --- /dev/null +++ b/unit-test/exceptions.js @@ -0,0 +1,32 @@ +/** + * This file points out anomalies/exceptions in test files when generating the binding.cc file + * + * nouns: words in file names that are misspelled + * *NOTE: a 'constructor' property is explicitly added to override javascript object constructor + * + * exportNames: anomalies in init function names + * + * propertyNames: anomalies in exported property name of init functions + * + * skipBinding: skip including this file in binding.cc + */ +module.exports = { + nouns: { + constructor: 'constructor', + threadsafe: 'threadSafe', + objectwrap: 'objectWrap' + }, + exportNames: { + AsyncWorkerPersistent: 'PersistentAsyncWorker' + }, + propertyNames: { + async_worker_persistent: 'persistentasyncworker', + objectwrap_constructor_exception: 'objectwrapConstructorException' + }, + skipBinding: [ + 'global_object_delete_property', + 'global_object_get_property', + 'global_object_has_own_property', + 'global_object_set_property' + ] +}; diff --git a/unit-test/generate-binding-cc.js b/unit-test/generate-binding-cc.js new file mode 100644 index 000000000..75ef477d2 --- /dev/null +++ b/unit-test/generate-binding-cc.js @@ -0,0 +1,61 @@ +const listOfTestModules = require('./listOfTestModules'); +const exceptions = require('./exceptions'); +const { generateFileContent, writeToBindingFile } = require('./binding-file-template'); + +const buildDirs = listOfTestModules.dirs; +const buildFiles = listOfTestModules.files; + +/** + * @param none + * @requires list of files to bind as command-line argument + * @returns list of binding configurations + */ +function generateBindingConfigurations () { + const testFilesToBind = process.argv.slice(2); + console.log('test modules to bind: ', testFilesToBind); + + const configs = []; + + testFilesToBind.forEach((file) => { + const configName = file.split('.cc')[0]; + + if (buildDirs[configName]) { + for (const file of buildDirs[configName]) { + if (exceptions.skipBinding.includes(file)) continue; + configs.push(buildFiles[file]); + } + } else if (buildFiles[configName]) { + configs.push(buildFiles[configName]); + } else { + console.log('not found', file, configName); + } + }); + + return Promise.resolve(configs); +} + +generateBindingConfigurations().then(generateFileContent).then(writeToBindingFile); + +/** + * Test cases + * @fires only when run directly from terminal with TEST=true + * eg: TEST=true node generate-binding-cc + */ +if (require.main === module && process.env.TEST === 'true') { + const assert = require('assert'); + + const setArgsAndCall = (fn, filterCondition) => { process.argv = [null, null, ...filterCondition.split(' ')]; return fn(); }; + const assertPromise = (promise, expectedVal) => promise.then((val) => assert.deepEqual(val, expectedVal)).catch(console.log); + + const expectedVal = [{ + dir: '', + objectName: 'AsyncProgressWorker', + propertyName: 'async_progress_worker' + }, + { + dir: '', + objectName: 'PersistentAsyncWorker', + propertyName: 'persistentasyncworker' + }]; + assertPromise(setArgsAndCall(generateBindingConfigurations, 'async_progress_worker async_worker_persistent'), expectedVal); +} diff --git a/unit-test/injectTestParams.js b/unit-test/injectTestParams.js new file mode 100644 index 000000000..11a054a13 --- /dev/null +++ b/unit-test/injectTestParams.js @@ -0,0 +1,101 @@ +const fs = require('fs'); +const path = require('path'); + +const listOfTestModules = require('./listOfTestModules'); + +const buildDirs = listOfTestModules.dirs; +const buildFiles = listOfTestModules.files; + +if (!fs.existsSync('./generated')) { + fs.mkdirSync('./generated'); +} + +/** + * @returns : list of files to compile by node-gyp + * @param : none + * @requires : picks `filter` parameter from process.env + * This function is used as an utility method to inject a list of files to compile into binding.gyp + */ +module.exports.filesToCompile = function () { + // match filter argument with available test modules + const matchedModules = require('./matchModules').matchWildCards(process.env.npm_config_filter || ''); + + // standard list of files to compile + const addedFiles = './generated/binding.cc test_helper.h'; + + const filterConditions = matchedModules.split(' ').length ? matchedModules.split(' ') : [matchedModules]; + const files = []; + + // generate a list of all files to compile + for (const matchCondition of filterConditions) { + if (buildDirs[matchCondition.toLowerCase()]) { + for (const file of buildDirs[matchCondition.toLowerCase()]) { + const config = buildFiles[file]; + const separator = config.dir.length ? '/' : ''; + files.push(config.dir + separator + file); + } + } else if (buildFiles[matchCondition.toLowerCase()]) { + const config = buildFiles[matchCondition.toLowerCase()]; + const separator = config.dir.length ? '/' : ''; + files.push(config.dir + separator + matchCondition.toLowerCase()); + } + } + + // generate a string of files to feed to the compiler + let filesToCompile = ''; + files.forEach((file) => { + filesToCompile = `${filesToCompile} ../test/${file}.cc`; + }); + + // log list of compiled files + fs.writeFileSync(path.join(__dirname, '/generated/compilelist'), `${addedFiles} ${filesToCompile}`.split(' ').join('\r\n')); + + // return file list + return `${addedFiles} ${filesToCompile}`; +}; + +/** + * @returns list of test files to bind exported init functions + * @param : none + * @requires : picks `filter` parameter from process.env + * This function is used as an utility method by the generateBindingCC step in binding.gyp + */ +module.exports.filesForBinding = function () { + const filterCondition = require('./matchModules').matchWildCards(process.env.npm_config_filter || ''); + fs.writeFileSync(path.join(__dirname, '/generated/bindingList'), filterCondition.split(' ').join('\r\n')); + return filterCondition; +}; + +/** + * Test cases + * @fires only when run directly from terminal + * eg: node injectTestParams + */ +if (require.main === module) { + const assert = require('assert'); + + const setEnvAndCall = (fn, filterCondition) => { process.env.npm_config_filter = filterCondition; return fn(); }; + + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'typed*ex*'), './generated/binding.cc test_helper.h ../test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc'); + + const expectedFilesToMatch = [ + './generated/binding.cc test_helper.h ', + '../test/threadsafe_function/threadsafe_function.cc', + '../test/threadsafe_function/threadsafe_function_ctx.cc', + '../test/threadsafe_function/threadsafe_function_existing_tsfn.cc', + '../test/threadsafe_function/threadsafe_function_ptr.cc', + '../test/threadsafe_function/threadsafe_function_sum.cc', + '../test/threadsafe_function/threadsafe_function_unref.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_sum.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_unref.cc' + ]; + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'threadsafe_function typed_threadsafe_function'), expectedFilesToMatch.join(' ')); + + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'objectwrap'), './generated/binding.cc test_helper.h ../test/objectwrap.cc'); + + console.log('ALL tests passed'); +} diff --git a/unit-test/listOfTestModules.js b/unit-test/listOfTestModules.js new file mode 100644 index 000000000..13a7e6183 --- /dev/null +++ b/unit-test/listOfTestModules.js @@ -0,0 +1,88 @@ +const fs = require('fs'); +const path = require('path'); +const exceptions = require('./exceptions'); + +const buildFiles = {}; +const buidDirs = {}; + +/** + * @param fileName - expect to be in snake case , eg: this_is_a_test_file.cc + * @returns init function name in the file + * + * general format of init function name is camelCase version of the snake_case file name + */ +function getExportObjectName (fileName) { + fileName = fileName.split('_').map(token => exceptions.nouns[token] ? exceptions.nouns[token] : token).join('_'); + const str = fileName.replace(/(_\w)/g, (k) => k[1].toUpperCase()); + const exportObjectName = str.charAt(0).toUpperCase() + str.substring(1); + if (exceptions.exportNames[exportObjectName]) { + return exceptions.exportNames[exportObjectName]; + } + return exportObjectName; +} + +/** + * @param fileName - expect to be in snake case , eg: this_is_a_test_file.cc + * @returns property name of exported init function + */ +function getExportPropertyName (fileName) { + if (exceptions.propertyNames[fileName.toLowerCase()]) { + return exceptions.propertyNames[fileName.toLowerCase()]; + } + return fileName; +} + +/** + * creates a configuration list for all available test modules + * The configuration object contains the expected init function names and corresponding export property names + */ +function listOfTestModules (currentDirectory = path.join(__dirname, '/../test'), pre = '') { + fs.readdirSync(currentDirectory).forEach((file) => { + if (file === 'binding.cc' || + file === 'binding.gyp' || + file === 'build' || + file === 'common' || + file === 'thunking_manual.cc' || + file === 'addon_build' || + file[0] === '.') { + return; + } + const absoluteFilepath = path.join(currentDirectory, file); + const fileName = file.toLowerCase().replace('.cc', ''); + if (fs.statSync(absoluteFilepath).isDirectory()) { + buidDirs[fileName] = []; + listOfTestModules(absoluteFilepath, pre + file + '/'); + } else { + if (!file.toLowerCase().endsWith('.cc')) return; + if (currentDirectory.trim().split('/test/').length > 1) { + buidDirs[currentDirectory.split('/test/')[1].toLowerCase()].push(fileName); + } + const relativePath = (currentDirectory.split(`${fileName}.cc`)[0]).split('/test/')[1] || ''; + buildFiles[fileName] = { dir: relativePath, propertyName: getExportPropertyName(fileName), objectName: getExportObjectName(fileName) }; + } + }); +} +listOfTestModules(); + +module.exports = { + dirs: buidDirs, + files: buildFiles +}; + +/** + * Test cases + * @fires only when run directly from terminal + * eg: node listOfTestModules + */ +if (require.main === module) { + const assert = require('assert'); + assert.strictEqual(getExportObjectName('objectwrap_constructor_exception'), 'ObjectWrapConstructorException'); + assert.strictEqual(getExportObjectName('typed_threadsafe_function'), 'TypedThreadSafeFunction'); + assert.strictEqual(getExportObjectName('objectwrap_removewrap'), 'ObjectWrapRemovewrap'); + assert.strictEqual(getExportObjectName('function_reference'), 'FunctionReference'); + assert.strictEqual(getExportObjectName('async_worker'), 'AsyncWorker'); + assert.strictEqual(getExportObjectName('async_progress_worker'), 'AsyncProgressWorker'); + assert.strictEqual(getExportObjectName('async_worker_persistent'), 'PersistentAsyncWorker'); + + console.log('ALL tests passed'); +} diff --git a/unit-test/matchModules.js b/unit-test/matchModules.js new file mode 100644 index 000000000..ce8317c91 --- /dev/null +++ b/unit-test/matchModules.js @@ -0,0 +1,65 @@ +const listOfTestModules = require('./listOfTestModules'); +const buildDirs = listOfTestModules.dirs; +const buildFiles = listOfTestModules.files; + +function isWildcard (filter) { + if (filter.includes('*')) return true; + return false; +} + +function filterBy (wildcard, item) { + return new RegExp('^' + wildcard.replace(/\*/g, '.*') + '$').test(item); +} + +/** + * @param filterCondition + * matches all given wildcards with available test modules to generate an elaborate filter condition + */ +function matchWildCards (filterCondition) { + const conditions = filterCondition.split(' ').length ? filterCondition.split(' ') : [filterCondition]; + const matches = []; + + for (const filter of conditions) { + if (isWildcard(filter)) { + const matchedDirs = Object.keys(buildDirs).filter(e => filterBy(filter, e)); + if (matchedDirs.length) { + matches.push(matchedDirs.join(' ')); + } + const matchedModules = Object.keys(buildFiles).filter(e => filterBy(filter, e)); + if (matchedModules.length) { matches.push(matchedModules.join(' ')); } + } else { + matches.push(filter); + } + } + + return matches.join(' '); +} + +module.exports.matchWildCards = matchWildCards; + +/** + * Test cases + * @fires only when run directly from terminal + * eg: node matchModules + */ +if (require.main === module) { + const assert = require('assert'); + + assert.strictEqual(matchWildCards('typed*ex'), 'typed*ex'); + assert.strictEqual(matchWildCards('typed*ex*'), 'typed_threadsafe_function_existing_tsfn'); + assert.strictEqual(matchWildCards('async*'), 'async_context async_progress_queue_worker async_progress_worker async_worker async_worker_persistent'); + assert.strictEqual(matchWildCards('typed*func'), 'typed*func'); + assert.strictEqual(matchWildCards('typed*func*'), 'typed_threadsafe_function'); + assert.strictEqual(matchWildCards('typed*function'), 'typed_threadsafe_function'); + assert.strictEqual(matchWildCards('object*inh'), 'object*inh'); + assert.strictEqual(matchWildCards('object*inh*'), 'objectwrap_multiple_inheritance'); + assert.strictEqual(matchWildCards('*remove*'), 'objectwrap_removewrap'); + assert.strictEqual(matchWildCards('*function'), 'threadsafe_function typed_threadsafe_function'); + assert.strictEqual(matchWildCards('**function'), 'threadsafe_function typed_threadsafe_function'); + assert.strictEqual(matchWildCards('a*w*p*'), 'async_worker_persistent'); + assert.strictEqual(matchWildCards('fun*ref'), 'fun*ref'); + assert.strictEqual(matchWildCards('fun*ref*'), 'function_reference'); + assert.strictEqual(matchWildCards('*reference'), 'function_reference object_reference reference'); + + console.log('ALL tests passed'); +} diff --git a/unit-test/setup.js b/unit-test/setup.js new file mode 100644 index 000000000..2e1a66960 --- /dev/null +++ b/unit-test/setup.js @@ -0,0 +1,13 @@ +const fs = require('fs'); +const { generateFileContent, writeToBindingFile } = require('./binding-file-template'); + +/** + * @summary setup script to execute before node-gyp begins target actions + */ +if (!fs.existsSync('./generated')) { + // create generated folder + fs.mkdirSync('./generated'); + // create empty binding.cc file + generateFileContent([]).then(writeToBindingFile); + // FIX: Its necessary to have an empty bindng.cc file, otherwise build fails first time +} diff --git a/unit-test/spawnTask.js b/unit-test/spawnTask.js new file mode 100644 index 000000000..d932af2cb --- /dev/null +++ b/unit-test/spawnTask.js @@ -0,0 +1,26 @@ +const { spawn } = require('child_process'); + +/** + * spawns a child process to run a given node.js script + */ +module.exports.runChildProcess = function (scriptName, options) { + const childProcess = spawn('node', [scriptName], options); + + childProcess.stdout.on('data', data => { + console.log(`${data}`); + }); + childProcess.stderr.on('data', data => { + console.log(`error: ${data}`); + }); + + return new Promise((resolve, reject) => { + childProcess.on('error', (error) => { + console.log(`error: ${error.message}`); + reject(error); + }); + childProcess.on('close', code => { + console.log(`child process exited with code ${code}`); + resolve(code); + }); + }); +}; diff --git a/unit-test/test.js b/unit-test/test.js new file mode 100644 index 000000000..0fcab6edf --- /dev/null +++ b/unit-test/test.js @@ -0,0 +1,30 @@ +'use strict'; +const path = require('path'); +const runChildProcess = require('./spawnTask').runChildProcess; + +/* +* Execute tests with given filter conditions as a child process +*/ +const executeTests = async function () { + try { + const workingDir = path.join(__dirname, '../'); + const relativeBuildPath = path.join('../', 'unit-test'); + const buildPath = path.join(__dirname, './unit-test'); + const envVars = { ...process.env, REL_BUILD_PATH: relativeBuildPath, BUILD_PATH: buildPath }; + + console.log('Starting to run tests in ', buildPath, new Date()); + + const code = await runChildProcess('test', { cwd: workingDir, env: envVars }); + + if (code !== '0') { + process.exitCode = code; + process.exit(process.exitCode); + } + + console.log('Completed running tests', new Date()); + } catch (e) { + console.log('Error occured running tests', new Date()); + } +}; + +executeTests();