diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 6bace6488..08c5896e2 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 494c7c212..40baa5ea1 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -12,7 +12,7 @@ env: NDK_VERSION: r27d ANDROID_API: 33 ANDROID_ABI: x86_64 - NDK_ARCH: darwin + NDK_ARCH: linux permissions: contents: read @@ -20,17 +20,17 @@ permissions: jobs: build: name: Build - runs-on: macos-15 + runs-on: ubuntu-latest outputs: npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }} npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 submodules: true @@ -38,16 +38,13 @@ jobs: with: node-version: 22 registry-url: "https://registry.npmjs.org" - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: "temurin" java-version: "21" cache: gradle - name: Setup Android SDK uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - - name: Homebrew dependencies - run: | - brew install wget - name: Setup NDK run: | echo "y" | sdkmanager "cmake;3.6.4111459" @@ -82,42 +79,39 @@ jobs: - name: Build npm package run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace - name: Upload npm package artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: npm-package path: dist/nativescript-android-${{steps.npm_version_output.outputs.NPM_VERSION}}.tgz - name: Upload debug symbols - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: debug-symbols path: test-app/runtime/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/* test: name: Test - runs-on: macos-15-intel + runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22 registry-url: "https://registry.npmjs.org" - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: "temurin" java-version: "21" cache: gradle - name: Setup Android SDK uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - - name: Homebrew dependencies - run: | - brew install wget - name: Setup NDK run: | echo "y" | sdkmanager "cmake;3.6.4111459" @@ -135,6 +129,11 @@ jobs: npm install --prefix ./test-app/tools - name: SBG tests run: ./gradlew runSbgTests --stacktrace + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Run unit tests uses: ReactiveCircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 with: @@ -145,7 +144,7 @@ jobs: script: ./gradlew runtestsAndVerifyResults --stacktrace - name: Upload Test Results if: ${{ !cancelled() }} # run this step even if previous step failed - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: android-unit-test-results path: test-app/dist/android_unit_test_results.xml @@ -163,7 +162,7 @@ jobs: NPM_TAG: ${{needs.build.outputs.npm_tag}} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit @@ -213,11 +212,11 @@ jobs: NPM_VERSION: ${{needs.build.outputs.npm_version}} steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 @@ -238,7 +237,7 @@ jobs: run: zip -r debug-symbols.zip . - name: Partial Changelog run: npx conventional-changelog -p angular -r2 > body.md - - uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 + - uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 with: artifacts: "dist/nativescript-android-*.tgz,dist/debug-symbols/debug-symbols.zip" bodyFile: "body.md" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index baad8bd98..aaa36a3eb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,7 +8,7 @@ env: NDK_VERSION: r27d ANDROID_API: 33 ANDROID_ABI: x86_64 - NDK_ARCH: darwin + NDK_ARCH: linux permissions: contents: read @@ -16,12 +16,12 @@ permissions: jobs: build: name: Build - runs-on: macos-15 + runs-on: ubuntu-latest outputs: npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }} npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }} steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 submodules: true @@ -29,16 +29,13 @@ jobs: with: node-version: 22 registry-url: "https://registry.npmjs.org" - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: "temurin" java-version: "21" cache: gradle - name: Setup Android SDK uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - - name: Homebrew dependencies - run: | - brew install wget - name: Setup NDK run: | echo "y" | sdkmanager "cmake;3.6.4111459" @@ -73,36 +70,33 @@ jobs: - name: Build npm package run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace - name: Upload npm package artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: npm-package path: dist/nativescript-android-${{steps.npm_version_output.outputs.NPM_VERSION}}.tgz - name: Upload debug symbols - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: debug-symbols path: test-app/runtime/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/* test: name: Test - runs-on: macos-15-intel + runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22 registry-url: "https://registry.npmjs.org" - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: "temurin" java-version: "21" cache: gradle - name: Setup Android SDK uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - - name: Homebrew dependencies - run: | - brew install wget - name: Setup NDK run: | echo "y" | sdkmanager "cmake;3.6.4111459" @@ -120,6 +114,11 @@ jobs: npm install --prefix ./test-app/tools - name: SBG tests run: ./gradlew runSbgTests --stacktrace + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Run unit tests uses: ReactiveCircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 with: @@ -130,7 +129,7 @@ jobs: script: ./gradlew runtestsAndVerifyResults --stacktrace - name: Upload Test Results if: ${{ !cancelled() }} # run this step even if previous step failed - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: android-unit-test-results path: test-app/dist/android_unit_test_results.xml diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index ea7725af1..841568e15 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -33,7 +33,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -60,7 +60,7 @@ jobs: # 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 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: sarif_file: results.sarif \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 83cb4c40f..7913129bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,4 @@ [submodule "test-app/build-tools/android-dts-generator"] path = test-app/build-tools/android-dts-generator url = https://github.com/NativeScript/android-dts-generator.git - branch = master + branch = main diff --git a/CHANGELOG.md b/CHANGELOG.md index df411bfb1..44ba189c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## [9.0.5](https://github.com/NativeScript/android/compare/v9.0.4...v9.0.5) (2026-07-13) + + +### Bug Fixes + +* anchor relative dynamic imports at the file:// referrer's directory ([#1976](https://github.com/NativeScript/android/issues/1976)) ([92c2654](https://github.com/NativeScript/android/commit/92c26548ee1111c5439bef4305227711022d5397)) +* **DexFactory:** add support for injecting DEX into parent class loader ([#1951](https://github.com/NativeScript/android/issues/1951)) ([c9d41e6](https://github.com/NativeScript/android/commit/c9d41e620b3413a67c1bd766e3f68e534a082814)) +* **DexFactory:** register injected proxy dex with a single class loader ([#1968](https://github.com/NativeScript/android/issues/1968)) ([fce8e29](https://github.com/NativeScript/android/commit/fce8e296c2345cc75d5a675e1beef5c0214ba2cf)), closes [pre-#1951](https://github.com/pre-/issues/1951) [#1962](https://github.com/NativeScript/android/issues/1962) [#1951](https://github.com/NativeScript/android/issues/1951) +* **jsparser:** skip non-Identifier keys in `.extend({})` argument ([#1950](https://github.com/NativeScript/android/issues/1950)) ([dd2984b](https://github.com/NativeScript/android/commit/dd2984bc6b64d06e13f626139e5b54b04a33cfb7)) +* normalize "." and ".." in resolved module paths to dedupe modules ([#1977](https://github.com/NativeScript/android/issues/1977)) ([45ed1f6](https://github.com/NativeScript/android/commit/45ed1f6bb567b407cfd6e579b0372aead9434289)) +* propagate custom ndkVersion to the runtime build ([#1974](https://github.com/NativeScript/android/issues/1974)) ([51e9fa0](https://github.com/NativeScript/android/commit/51e9fa00819e79ed71bd1635d40d781ca265aa71)) +* **timers:** order timers with the Java MessageQueue instead of ALooper fds ([bfd7650](https://github.com/NativeScript/android/commit/bfd765043aaa30cc1ef14d00e3cebfa04e980597)) +* URLSearchParams construction and iteration spec compliance ([#1970](https://github.com/NativeScript/android/issues/1970)) ([89893ae](https://github.com/NativeScript/android/commit/89893ae6961ee6d55037bd1f710c619a4282f817)) + + +### Features + +* **inspector:** attach Chrome DevTools to Web Worker isolates ([#1973](https://github.com/NativeScript/android/issues/1973)) ([4b5ab0a](https://github.com/NativeScript/android/commit/4b5ab0ac8426762af2092f1f405eb547a443719a)) +* **inspector:** serve source maps to DevTools via Network.loadNetwor… ([#1969](https://github.com/NativeScript/android/issues/1969)) ([55da2da](https://github.com/NativeScript/android/commit/55da2da87e1a0ef9cf35adc3cc5cba1fb8a69cd9)), closes [NativeScript/ios#385](https://github.com/NativeScript/ios/issues/385) [NativeScript/ios#378](https://github.com/NativeScript/ios/issues/378) [nodejs/node#58077](https://github.com/nodejs/node/issues/58077) +* **workers:** move worker threading and messaging to C++, mirroring the iOS runtime, and support SharedArrayBuffer ([#1972](https://github.com/NativeScript/android/issues/1972)) ([a84d3c7](https://github.com/NativeScript/android/commit/a84d3c7ab33f8c80c5f42af6d36e9763236d17b5)) + + + ## [9.0.4](https://github.com/NativeScript/android/compare/v9.0.3...v9.0.4) (2026-04-29) diff --git a/package.json b/package.json index e57f7a186..d31acb66c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@nativescript/android", "description": "NativeScript for Android using v8", - "version": "9.0.4", + "version": "9.0.5", "repository": { "type": "git", "url": "https://github.com/NativeScript/android.git" diff --git a/test-app/app/src/main/assets/app/esm-dedup/counter.mjs b/test-app/app/src/main/assets/app/esm-dedup/counter.mjs new file mode 100644 index 000000000..0d1750603 --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-dedup/counter.mjs @@ -0,0 +1,8 @@ +// Singleton module: an ES module is evaluated once, so every importer must +// observe this same `state` object regardless of how the specifier spelled the +// path to this file. +export const state = { count: 0 }; + +export function increment() { + state.count++; +} diff --git a/test-app/app/src/main/assets/app/esm-dedup/nested/viaParentDir.mjs b/test-app/app/src/main/assets/app/esm-dedup/nested/viaParentDir.mjs new file mode 100644 index 000000000..22131116d --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-dedup/nested/viaParentDir.mjs @@ -0,0 +1,6 @@ +// Reaches the same counter.mjs one directory up: "../counter.mjs". +import { state, increment } from "../counter.mjs"; + +increment(); + +export const seenState = state; diff --git a/test-app/app/src/main/assets/app/esm-dedup/viaSameDir.mjs b/test-app/app/src/main/assets/app/esm-dedup/viaSameDir.mjs new file mode 100644 index 000000000..a2de1ad06 --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-dedup/viaSameDir.mjs @@ -0,0 +1,6 @@ +// Reaches counter.mjs as a same-directory sibling: "./counter.mjs". +import { state, increment } from "./counter.mjs"; + +increment(); + +export const seenState = state; diff --git a/test-app/app/src/main/assets/app/esm-subdir/nested/child.mjs b/test-app/app/src/main/assets/app/esm-subdir/nested/child.mjs new file mode 100644 index 000000000..786d3b350 --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-subdir/nested/child.mjs @@ -0,0 +1,5 @@ +// Performs a "../" relative dynamic import reaching up one directory. +export async function loadParentSibling() { + const sibling = await import("../sibling.mjs"); + return sibling.value; +} diff --git a/test-app/app/src/main/assets/app/esm-subdir/parent.mjs b/test-app/app/src/main/assets/app/esm-subdir/parent.mjs new file mode 100644 index 000000000..01f911a0b --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-subdir/parent.mjs @@ -0,0 +1,7 @@ +// Performs a relative dynamic import of a sibling in the same subdirectory. +// The specifier "./sibling.mjs" must resolve against this module's directory, +// not the application root. +export async function loadSibling() { + const sibling = await import("./sibling.mjs"); + return sibling.value; +} diff --git a/test-app/app/src/main/assets/app/esm-subdir/sibling.mjs b/test-app/app/src/main/assets/app/esm-subdir/sibling.mjs new file mode 100644 index 000000000..4da8c2b58 --- /dev/null +++ b/test-app/app/src/main/assets/app/esm-subdir/sibling.mjs @@ -0,0 +1,2 @@ +// Sibling module reached via a relative dynamic import from the same directory. +export const value = "sibling-loaded"; diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 0a8c79b00..8184e7553 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -49,7 +49,9 @@ require("./tests/requireExceptionTests"); require("./tests/java-array-test"); require("./tests/field-access-test"); require("./tests/byte-buffer-test"); +require("./tests/shared-array-buffer-test"); require("./tests/dex-interface-implementation"); +require("./tests/testClassForNameDiscovery"); require("./tests/testInterfaceImplementation"); require("./tests/testRuntimeImplementedAPIs"); require("./tests/testsInstanceOfOperator"); diff --git a/test-app/app/src/main/assets/app/shared b/test-app/app/src/main/assets/app/shared index 0e030139e..3a262b979 160000 --- a/test-app/app/src/main/assets/app/shared +++ b/test-app/app/src/main/assets/app/shared @@ -1 +1 @@ -Subproject commit 0e030139e7273975106cbedd69681f55d2c2fbf2 +Subproject commit 3a262b979c6b84cdfe69cd495436a7088d016505 diff --git a/test-app/app/src/main/assets/app/testRelativeDynamicImport.mjs b/test-app/app/src/main/assets/app/testRelativeDynamicImport.mjs new file mode 100644 index 000000000..50f7fba62 --- /dev/null +++ b/test-app/app/src/main/assets/app/testRelativeDynamicImport.mjs @@ -0,0 +1,6 @@ +// Control: a relative dynamic import from an app-root module, where the +// referrer's directory is the application root. Must keep resolving. +export async function loadRootSibling() { + const mod = await import("./testSimpleESModule.mjs"); + return mod.moduleType; +} diff --git a/test-app/app/src/main/assets/app/tests/shared-array-buffer-test.js b/test-app/app/src/main/assets/app/tests/shared-array-buffer-test.js new file mode 100644 index 000000000..759d5223c --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/shared-array-buffer-test.js @@ -0,0 +1,91 @@ +describe("Tests SharedArrayBuffer conversion", function () { + it("should pass a SharedArrayBuffer to a Java method expecting ByteBuffer", function () { + var sab = new SharedArrayBuffer(8); + var view = new Uint8Array(sab); + for (var i = 0; i < 8; i++) { + view[i] = i + 1; + } + + // resolves the ByteBuffer.put(ByteBuffer) overload and copies from the + // direct buffer created over the SharedArrayBuffer's memory + var bb = java.nio.ByteBuffer.allocateDirect(8); + bb.put(sab); + bb.flip(); + + var roundTripped = new Uint8Array(ArrayBuffer.from(bb)); + for (var i = 0; i < 8; i++) { + expect(roundTripped[i]).toBe(i + 1); + } + }); + + it("should respect byteOffset and length of typed array views over a SharedArrayBuffer", function () { + var sab = new SharedArrayBuffer(16); + var full = new Uint8Array(sab); + for (var i = 0; i < 16; i++) { + full[i] = i; + } + + var slice = new Uint8Array(sab, 4, 8); // bytes 4..11 + + var bb = java.nio.ByteBuffer.allocateDirect(8); + bb.put(slice); + bb.flip(); + + var roundTripped = new Uint8Array(ArrayBuffer.from(bb)); + for (var i = 0; i < 8; i++) { + expect(roundTripped[i]).toBe(i + 4); + } + }); + + it("should share memory between the SharedArrayBuffer and the Java buffer (no copy)", function () { + var sab = new SharedArrayBuffer(4); + var view = new Uint8Array(sab); + view[0] = 42; + + // the holder keeps the direct ByteBuffer the runtime created over the + // SharedArrayBuffer's memory, so Java reads/writes go to the same bytes + var holder = new com.tns.tests.ByteBufferHolder(); + holder.hold(sab); + expect(holder.get(0)).toBe(42); + + // JS mutations after the call are visible through the Java buffer + view[0] = 99; + expect(holder.get(0)).toBe(99); + + // and Java mutations are visible through the SharedArrayBuffer + holder.put(1, 77); + expect(view[1]).toBe(77); + }); + + it("should share a SharedArrayBuffer's memory with a worker and a Java buffer at once", function (done) { + var sab = new SharedArrayBuffer(4); + var view = new Uint8Array(sab); + view[0] = 0; + + var holder = new com.tns.tests.ByteBufferHolder(); + holder.hold(sab); + + var worker = new Worker("../shared/Workers/EvalWorker.js"); + worker.postMessage({ + value: sab, + eval: "var v = new Uint8Array(value); v[0] = 42; v[3] = 99; postMessage('written');" + }); + // fail fast instead of waiting for the jasmine timeout if the worker + // errors before posting back + worker.onerror = function (e) { + expect("worker error: " + e.message).toBe(""); + worker.terminate(); + done(); + }; + worker.onmessage = function (msg) { + expect(msg.data).toBe("written"); + // the worker's write is visible both to this isolate and to Java + expect(view[0]).toBe(42); + expect(view[3]).toBe(99); + expect(holder.get(0)).toBe(42); + expect(holder.get(3)).toBe(99); + worker.terminate(); + done(); + }; + }); +}); diff --git a/test-app/app/src/main/assets/app/tests/testClassForNameDiscovery.js b/test-app/app/src/main/assets/app/tests/testClassForNameDiscovery.js new file mode 100644 index 000000000..4dcad0eec --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testClassForNameDiscovery.js @@ -0,0 +1,67 @@ +describe("Tests Class.forName discovery of runtime generated classes", function () { + + // Android framework components (e.g. FragmentFactory) resolve classes with + // Class.forName(className, false, context.getClassLoader()). Runtime generated + // proxies must be discoverable through the app's class loader, otherwise + // framework lookups crash with ClassNotFoundException (see issue #1962 / PR #1951). + // + // The extend calls below are built dynamically so the static binding generator + // cannot pre-generate the proxies and DexFactory.resolveClass takes the runtime + // generation + parent class loader injection path. + var ext = "ex" + "tend"; + + // the app's PathClassLoader — the same loader the framework uses, + // e.g. in FragmentFactory.loadFragmentClass via context.getClassLoader() + var appClassLoader = com.tns.Runtime.class.getClassLoader(); + + it("When_extending_a_class_at_runtime_it_should_be_discoverable_through_the_app_class_loader", function () { + var MyObject = java.lang.Object[ext]("ClassForNameDiscoveryObject", { + toString: function () { + return "discoverable"; + } + }); + + var instance = new MyObject(); + var className = instance.getClass().getName(); + + var found = java.lang.Class.forName(className, false, appClassLoader); + + expect(found.getName()).toBe(className); + expect(found.equals(instance.getClass())).toBe(true); + }); + + it("When_implementing_an_interface_at_runtime_it_should_be_discoverable_through_the_app_class_loader", function () { + var MyRunnable = java.lang.Runnable[ext]("ClassForNameDiscoveryRunnable", { + run: function () { + } + }); + + var instance = new MyRunnable(); + var className = instance.getClass().getName(); + + var found = java.lang.Class.forName(className, false, appClassLoader); + + expect(found.getName()).toBe(className); + expect(found.equals(instance.getClass())).toBe(true); + }); + + it("When_a_runtime_generated_class_is_instantiated_through_reflection_it_should_dispatch_to_the_JS_implementation", function () { + var MyObject = java.lang.Object[ext]("ClassForNameDiscoveryInstantiated", { + toString: function () { + return "created via reflection"; + } + }); + + // make sure the implementation object is registered before Java constructs an instance + var instance = new MyObject(); + var className = instance.getClass().getName(); + + // FragmentFactory resolves the class by name and instantiates it through + // reflection. Class.newInstance() invokes the no-arg constructor without + // the varargs marshalling getDeclaredConstructor() would need. + var found = java.lang.Class.forName(className, false, appClassLoader); + var created = found.newInstance(); + + expect(created.toString()).toBe("created via reflection"); + }); +}); diff --git a/test-app/app/src/main/assets/app/tests/testESModules.mjs b/test-app/app/src/main/assets/app/tests/testESModules.mjs index 02aaf7627..23e932f26 100644 --- a/test-app/app/src/main/assets/app/tests/testESModules.mjs +++ b/test-app/app/src/main/assets/app/tests/testESModules.mjs @@ -31,4 +31,51 @@ describe("ES Modules", () => { expect(workerResults.urlObjectSupported).toBe(true); expect(workerResults.tildePathSupported).toBe(true); }); + + // These use the done-callback form: this Jasmine version only awaits a spec + // when its function declares an argument (an async function returning a + // promise is run synchronously and its result ignored). + it("resolves a relative dynamic import from a subdirectory module", (done) => { + import("~/esm-subdir/parent.mjs") + .then((parent) => parent.loadSibling()) + .then( + (value) => { expect(value).toBe("sibling-loaded"); done(); }, + (err) => { expect(err).toBeUndefined(); done(); } + ); + }); + + it("resolves a '../' relative dynamic import from a nested module", (done) => { + import("~/esm-subdir/nested/child.mjs") + .then((child) => child.loadParentSibling()) + .then( + (value) => { expect(value).toBe("sibling-loaded"); done(); }, + (err) => { expect(err).toBeUndefined(); done(); } + ); + }); + + it("still resolves a relative dynamic import from an app-root module", (done) => { + import("~/testRelativeDynamicImport.mjs") + .then((root) => root.loadRootSibling()) + .then( + (value) => { expect(value).toBe("ES Module"); done(); }, + (err) => { expect(err).toBeUndefined(); done(); } + ); + }); + + it("resolves './x' and '../x' to a single shared module instance", (done) => { + Promise.all([ + import("~/esm-dedup/viaSameDir.mjs"), + import("~/esm-dedup/nested/viaParentDir.mjs"), + ]).then( + ([sameDir, parentDir]) => { + // The same counter.mjs is reached as "./counter.mjs" and as + // "../counter.mjs"; it must be one module instance sharing one state + // object, incremented once per importer. + expect(sameDir.seenState).toBe(parentDir.seenState); + expect(sameDir.seenState.count).toBe(2); + done(); + }, + (err) => { expect(err).toBeUndefined(); done(); } + ); + }); }); diff --git a/test-app/app/src/main/assets/app/tests/testNativeTimers.js b/test-app/app/src/main/assets/app/tests/testNativeTimers.js index 58af54916..11b7db766 100644 --- a/test-app/app/src/main/assets/app/tests/testNativeTimers.js +++ b/test-app/app/src/main/assets/app/tests/testNativeTimers.js @@ -94,6 +94,49 @@ describe('native timer', () => { done(); }); }); + // these specs schedule from a java-posted runnable so they run outside any + // timer callback: jasmine chains specs through timer callbacks, and when + // the runtime is built with NS_TIMERS_NESTING_CLAMP the nesting clamp + // (>=5 levels -> 4ms minimum) would otherwise make setTimeout(0) + // legitimately lose to a postDelayed(0) + it('preserves order with java handler posts', (done) => { + const order = []; + const handler = new android.os.Handler(android.os.Looper.myLooper()); + handler.post(new java.lang.Runnable({ + run: () => { + setTimeout(() => order.push(1)); + handler.postDelayed(new java.lang.Runnable({ run: () => order.push(2) }), 0); + setTimeout(() => order.push(3)); + setTimeout(() => { + expect(order.join(',')).toBe('1,2,3'); + done(); + }, 100); + } + })); + }); + + it('interleaves many timers with a java handler post', (done) => { + const order = []; + const handler = new android.os.Handler(android.os.Looper.myLooper()); + handler.post(new java.lang.Runnable({ + run: () => { + for (let i = 0; i < 50; i++) { + setTimeout(() => order.push('t')); + } + handler.postDelayed(new java.lang.Runnable({ run: () => order.push('j') }), 0); + for (let i = 0; i < 50; i++) { + setTimeout(() => order.push('t')); + } + setTimeout(() => { + // the java runnable must land exactly between the two timer batches + expect(order.indexOf('j')).toBe(50); + expect(order.length).toBe(101); + done(); + }, 100); + } + })); + }); + it('frees up resources after complete', (done) => { let timeout = 0; let interval = 0; diff --git a/test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js b/test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js index c55d356c5..501cf4e61 100644 --- a/test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js +++ b/test-app/app/src/main/assets/app/tests/testRuntimeImplementedAPIs.js @@ -1,32 +1,33 @@ describe("Runtime exposes", function () { it("__time a low overhead, high resolution, time in ms.", function () { - // Try to get the times using Date.now and __time and compare the results, expect them to be somewhat "close". - // Sometimes GC hits after Date.now is captured but before __time or the vice-versa and the test fails, - // so we are giving it several attempts. - for (var i = 0; i < 5; i++) { - try { - var dateTimeStart = Date.now(); - var timeStart = __time(); - var acc = 0; - var s = android.os.SystemClock.elapsedRealtime(); - for (var i = 0; i < 1000; i++) { - var c = android.os.SystemClock.elapsedRealtime(); - acc += c - s; - s = c; - } - var dateTimeEnd = Date.now(); - var timeEnd = __time(); - var dateDelta = dateTimeEnd - dateTimeStart; - var timeDelta = timeEnd - timeStart; - var tolerance = Math.max(10, dateDelta * 0.5); - expect(timeDelta > 0).toBe(true); - expect(Math.abs(dateDelta - timeDelta) < tolerance).toBe(true); - break; - } catch (e) { - if (i == 4) { - throw e; - } + // Compare the Date.now and __time deltas over the same interval and expect + // them to be somewhat "close". A GC pause or scheduler preemption landing + // between the capture of the two clocks skews a single measurement, so the + // window is kept wide enough for the proportional tolerance to absorb + // realistic pauses, and the measurement is retried a few times with the + // expectation only recorded once (jasmine expectations don't throw, so a + // try/catch based retry would record the failed attempts anyway). + var attempts = 5; + var dateDelta, timeDelta, tolerance; + var ok = false; + + for (var attempt = 0; attempt < attempts && !ok; attempt++) { + var dateTimeStart = Date.now(); + var timeStart = __time(); + + while (Date.now() - dateTimeStart < 50) { + // busy-wait to widen the measured interval } + + var dateTimeEnd = Date.now(); + var timeEnd = __time(); + + dateDelta = dateTimeEnd - dateTimeStart; + timeDelta = timeEnd - timeStart; + tolerance = Math.max(10, dateDelta * 0.5); + ok = timeDelta > 0 && Math.abs(dateDelta - timeDelta) < tolerance; } + + expect(ok).toBe(true, "__time delta " + timeDelta + "ms diverged from Date.now delta " + dateDelta + "ms (tolerance " + tolerance + "ms) on all " + attempts + " attempts"); }); }); diff --git a/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js b/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js index 3d0ca293f..b326af9a3 100644 --- a/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js +++ b/test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js @@ -1,15 +1,16 @@ describe("Test URLSearchParams ", function () { const fooBar = "foo=1&bar=2"; it("Test URLSearchParams keys", function(){ + // keys() returns a spec iterator, not an array — consume it via spread. const params = new URLSearchParams(fooBar); - const keys = params.keys(); + const keys = [...params.keys()]; expect(keys[0]).toBe("foo"); expect(keys[1]).toBe("bar"); }); it("Test URLSearchParams values", function(){ const params = new URLSearchParams(fooBar); - const values = params.values(); + const values = [...params.values()]; expect(values[0]).toBe("1"); expect(values[1]).toBe("2"); }); @@ -17,7 +18,7 @@ describe("Test URLSearchParams ", function () { it("Test URLSearchParams entries", function(){ const params = new URLSearchParams(fooBar); - const entries = params.entries(); + const entries = [...params.entries()]; expect(entries[0][0]).toBe("foo"); expect(entries[0][1]).toBe("1"); @@ -26,6 +27,74 @@ describe("Test URLSearchParams ", function () { }); + it("Test URLSearchParams keys/values/entries return spec iterators", function(){ + const params = new URLSearchParams(fooBar); + // A spec iterator has a next() and is itself iterable. + expect(typeof params.entries().next).toBe("function"); + expect(typeof params.keys().next).toBe("function"); + expect(typeof params.values().next).toBe("function"); + const it = params.entries(); + const first = it.next(); + expect(first.done).toBe(false); + expect(first.value[0]).toBe("foo"); + expect(first.value[1]).toBe("1"); + }); + + it("Test URLSearchParams entries preserves duplicate keys", function(){ + // Regression: the old get_keys()+get() path returned the first value for + // every occurrence of a repeated key. + const params = new URLSearchParams("a=1&a=2&b=3"); + const entries = [...params.entries()]; + expect(entries.length).toBe(3); + expect(entries[0][1]).toBe("1"); + expect(entries[1][1]).toBe("2"); + expect(entries[2][1]).toBe("3"); + expect([...params.values()].join(",")).toBe("1,2,3"); + }); + + it("Test URLSearchParams default iterator aliases entries", function(){ + // The default @@iterator IS the entries method (browser identity). This binding + // installs members per-instance (not on the prototype), so assert on an instance + // AND assert it is actually a function — not the vacuous undefined === undefined. + const params = new URLSearchParams(fooBar); + expect(typeof params[Symbol.iterator]).toBe("function"); + expect(params[Symbol.iterator]).toBe(params.entries); + }); + + it("Test URLSearchParams iterator carries the spec brand", function(){ + const params = new URLSearchParams(fooBar); + expect(Object.prototype.toString.call(params.entries())).toBe("[object URLSearchParams Iterator]"); + }); + + it("Test URLSearchParams iterator is live", function(){ + // Spec iterators reflect mutations made after they are created. + const params = new URLSearchParams("a=1&b=2"); + const it = params.entries(); + expect(it.next().value[0]).toBe("a"); // consume "a" + params.append("c", "3"); // mutate mid-iteration + const rest = []; + let r; + while (!(r = it.next()).done) { + rest.push(r.value[0]); + } + expect(rest.join(",")).toBe("b,c"); // sees the appended "c" + }); + + it("Test URLSearchParams closes the source iterator on a bad pair", function(){ + // On an abrupt completion (a too-long pair) the source iterator must be + // closed, so a generator's finally runs and resource-backed iterables free. + let closed = false; + function* gen() { + try { + yield ["a", "1", "2"]; // 3-element pair → TypeError + } finally { + closed = true; + } + } + expect(function(){ new URLSearchParams(gen()); }).toThrow(); + expect(closed).toBe(true); + }); + it("Test URLSearchParams size", function(){ const params = new URLSearchParams(fooBar); @@ -43,7 +112,27 @@ describe("Test URLSearchParams ", function () { const params = new URLSearchParams(fooBar); params.append("first", "Osei"); params.delete("first"); - expect(params.get("first")).toBe(undefined); + // Spec: get() returns null for a missing name (url.bs:4016). + expect(params.get("first")).toBe(null); + }); + + it("Test URLSearchParams get returns null for a missing name", function(){ + // Spec get(name): "...otherwise null" (url.bs:4016). + const params = new URLSearchParams("a=1"); + expect(params.get("missing")).toBe(null); + }); + + it("Test URLSearchParams delete with value removes only matching pairs", function(){ + // Spec delete(name, value): when value is given, remove only tuples + // matching BOTH name and value (url.bs:4000). The value is a USVString, + // so a non-string (the number 1) coerces to "1". + const params = new URLSearchParams("a=1&a=2&a=1&b=1"); + params.delete("a", 1); + expect(params.getAll("a").join(",")).toBe("2"); + expect(params.getAll("b").join(",")).toBe("1"); + // Single-arg delete still removes every pair with that name. + params.delete("a"); + expect(params.has("a")).toBe(false); }); @@ -52,6 +141,47 @@ describe("Test URLSearchParams ", function () { expect(params.has("foo")).toBe(true); }); + it("Test URLSearchParams has with value matches name and value", function(){ + // Spec has(name, value): true only for a tuple matching BOTH (url.bs:4028). + // The value is a USVString, so non-strings (number, boolean) coerce. + const params = new URLSearchParams("a=1&a=2&flag=true"); + expect(params.has("a", "1")).toBe(true); + expect(params.has("a", 2)).toBe(true); // number coerces to "2" + expect(params.has("a", "3")).toBe(false); + expect(params.has("flag", true)).toBe(true); // boolean coerces to "true" + expect(params.has("missing", "1")).toBe(false); + // Single-arg has still matches by name only. + expect(params.has("a")).toBe(true); + }); + + it("Test URLSearchParams has/delete throw when the value cannot be coerced", function(){ + // The value argument is a USVString; a Symbol (or a throwing toString) + // cannot convert, so the call must throw rather than silently matching "" + // (WebIDL USVString conversion, url.bs:4000 / 4028). + const params = new URLSearchParams("a=1"); + expect(function(){ params.has("a", Symbol("x")); }).toThrow(); + expect(function(){ params.delete("a", Symbol("x")); }).toThrow(); + }); + + it("Test URLSearchParams has/delete treat an explicit undefined value as omitted", function(){ + // Per WPT (urlsearchparams-has / -delete "respects undefined as second + // arg"), an explicit `undefined` second argument is treated as omitted + // (name-only), NOT as the string "undefined". + const params = new URLSearchParams("a=b&a=d&c&e&"); + expect(params.has("a", "b")).toBe(true); + expect(params.has("a", "c")).toBe(false); + expect(params.has("a", undefined)).toBe(true); // undefined -> name-only + + const del = new URLSearchParams(); + del.append("a", "b"); + del.append("a", "c"); + del.append("b", "c"); + del.append("b", "d"); + del.delete("b", "c"); + del.delete("a", undefined); // undefined -> delete by name + expect(del.toString()).toBe("b=d"); + }); + it("Test URLSearchParams changes propagates to URL parent", function(){ const toBe = 'https://github.com/triniwiz?first=Osei'; const url = new URL('https://github.com/triniwiz'); @@ -114,4 +244,279 @@ describe("Test URLSearchParams ", function () { expect(results[2].value).toBe("3"); }); + it("Test URLSearchParams from record object", function(){ + const params = new URLSearchParams({ foo: "1", bar: "2" }); + expect(params.get("foo")).toBe("1"); + expect(params.get("bar")).toBe("2"); + expect(params.size).toBe(2); + // A plain object must expand to its entries, not collapse into a + // single "[object Object]" key. + expect(params.has("[object Object]")).toBe(false); + }); + + it("Test URLSearchParams from record serializes every pair in toString", function(){ + const params = new URLSearchParams({ one: "1", two: "2" }); + expect(params.toString()).toBe("one=1&two=2"); + }); + + it("Test URLSearchParams from record coerces values to strings", function(){ + const params = new URLSearchParams({ a: 1, b: true }); + expect(params.get("a")).toBe("1"); + expect(params.get("b")).toBe("true"); + }); + + it("Test URLSearchParams from record encodes special characters", function(){ + const params = new URLSearchParams({ q: "a b&c" }); + expect(params.get("q")).toBe("a b&c"); + expect(params.toString()).toBe("q=a+b%26c"); + }); + + it("Test URLSearchParams from array of pairs", function(){ + const params = new URLSearchParams([["foo", "1"], ["bar", "2"], ["foo", "3"]]); + expect(params.get("foo")).toBe("1"); + expect(params.getAll("foo").length).toBe(2); + expect(params.get("bar")).toBe("2"); + expect(params.size).toBe(3); + }); + + it("Test URLSearchParams empty record and no-arg produce empty query", function(){ + expect(new URLSearchParams().toString()).toBe(""); + expect(new URLSearchParams({}).toString()).toBe(""); + }); + + it("Test URLSearchParams from record throws when a value cannot be coerced to a string", function(){ + // Per spec the record/sequence init coerces every value to a USVString; + // a value that cannot convert (a Symbol here) must throw rather than + // silently dropping or emptying the entry. + expect(function(){ new URLSearchParams({ bad: Symbol("x") }); }).toThrow(); + }); + + // --- Iterable (sequence) init: any iterable of pairs, not only arrays. --- + + it("Test URLSearchParams from a Map", function(){ + const params = new URLSearchParams(new Map([["a", "1"], ["b", "2"]])); + expect(params.toString()).toBe("a=1&b=2"); + }); + + it("Test URLSearchParams from a Set of pairs", function(){ + const params = new URLSearchParams(new Set([["x", "1"], ["y", "2"]])); + expect(params.toString()).toBe("x=1&y=2"); + }); + + it("Test URLSearchParams copy-constructs from another URLSearchParams", function(){ + // A URLSearchParams is iterable, so per spec it resolves to the sequence + // (copy) form — including duplicate keys, which proves the @@iterator walks + // entries rather than collapsing them. + const source = new URLSearchParams("a=1&a=2&b=3"); + const copy = new URLSearchParams(source); + expect(copy.toString()).toBe("a=1&a=2&b=3"); + expect(copy.getAll("a").length).toBe(2); + }); + + it("Test URLSearchParams from a generator of pairs", function(){ + function* pairs() { + yield ["a", "1"]; + yield ["b", "2"]; + } + const params = new URLSearchParams(pairs()); + expect(params.toString()).toBe("a=1&b=2"); + }); + + it("Test URLSearchParams from sequence with non-array inner pairs", function(){ + // Each pair need only be a 2-element iterable, not specifically an array. + // A Set is iterable but not an Array, so it exercises the inner iterator path. + const params = new URLSearchParams([new Set(["k", "v"])]); + expect(params.get("k")).toBe("v"); + }); + + it("Test URLSearchParams sequence init throws on a too-long pair", function(){ + expect(function(){ new URLSearchParams([["a", "1", "2"]]); }).toThrow(); + }); + + it("Test URLSearchParams sequence init throws on a too-short pair", function(){ + expect(function(){ new URLSearchParams([["a"]]); }).toThrow(); + }); + + it("Test URLSearchParams sequence init throws on a non-iterable element", function(){ + expect(function(){ new URLSearchParams([null]); }).toThrow(); + expect(function(){ new URLSearchParams([1]); }).toThrow(); + }); + + it("Test URLSearchParams sequence init throws on a primitive string element", function(){ + // WebIDL converts each element to sequence, whose first step throws + // when the element is not an Object. A 2-code-point string must NOT be accepted + // as the pair ("a","b"). + expect(function(){ new URLSearchParams(["ab"]); }).toThrow(); + }); + + it("Test URLSearchParams sequence init accepts a String-object element", function(){ + // A String *object* IS an Object and is iterable, so it is a valid 2-char pair. + const params = new URLSearchParams([new String("ab")]); + expect(params.get("a")).toBe("b"); + }); + + it("Test URLSearchParams throws when @@iterator is present but not callable", function(){ + // Per WebIDL GetMethod, a non-callable @@iterator is a TypeError, not a + // silent fall-back to the record form. + expect(function(){ new URLSearchParams({ [Symbol.iterator]: 5 }); }).toThrow(); + }); + + // --- The type itself is iterable. --- + + it("Test URLSearchParams is spread-iterable via Symbol.iterator", function(){ + const params = new URLSearchParams("a=1&b=2"); + const entries = [...params]; + expect(entries.length).toBe(2); + expect(entries[0][0]).toBe("a"); + expect(entries[0][1]).toBe("1"); + expect(entries[1][0]).toBe("b"); + expect(entries[1][1]).toBe("2"); + }); + + it("Test URLSearchParams works in a for..of loop", function(){ + const params = new URLSearchParams("a=1&a=2"); + const seen = []; + for (const [key, value] of params) { + seen.push(key + "=" + value); + } + expect(seen.length).toBe(2); + expect(seen[0]).toBe("a=1"); + expect(seen[1]).toBe("a=2"); + }); + + // --- Primitive init: coerced to USVString, then parsed. --- + + it("Test URLSearchParams from a number", function(){ + expect(new URLSearchParams(123).toString()).toBe("123="); + }); + + it("Test URLSearchParams from a boolean", function(){ + expect(new URLSearchParams(true).toString()).toBe("true="); + }); + + it("Test URLSearchParams from a bigint", function(){ + expect(new URLSearchParams(10n).toString()).toBe("10="); + }); + + it("Test URLSearchParams strips a single leading question mark", function(){ + expect(new URLSearchParams("?a=1").get("a")).toBe("1"); + }); + + it("Test URLSearchParams throws when init is a Symbol", function(){ + expect(function(){ new URLSearchParams(Symbol("x")); }).toThrow(); + }); + + it("Test URLSearchParams from undefined or no argument is empty", function(){ + expect(new URLSearchParams(undefined).toString()).toBe(""); + expect(new URLSearchParams().toString()).toBe(""); + }); + + it("Test URLSearchParams from null parses as the string null", function(){ + // The IDL union has no null special case (the type is not nullable and + // a record is not a dictionary), so null coerces like any primitive. + const params = new URLSearchParams(null); + expect(params.toString()).toBe("null="); + expect(params.get("null")).toBe(""); + }); + + it("Test URLSearchParams throws when a record key is a Symbol", function(){ + // Per WebIDL record conversion every own enumerable key is converted to + // a USVString, and converting a Symbol throws. + expect(function(){ new URLSearchParams({ a: "1", [Symbol("x")]: "v" }); }).toThrow(); + }); + + // --- The name argument is a USVString: coerced, not assumed. --- + + it("Test URLSearchParams coerces a non-string name in get/getAll/has/delete", function(){ + const params = new URLSearchParams("1=a&true=b&null=c"); + expect(params.get(1)).toBe("a"); + expect(params.getAll(1).length).toBe(1); + expect(params.getAll(1)[0]).toBe("a"); + expect(params.has(true)).toBe(true); + expect(params.get(null)).toBe("c"); + params.delete(true); + expect(params.has("true")).toBe(false); + expect(params.has("1")).toBe(true); + }); + + it("Test URLSearchParams coerces an object name via toString", function(){ + const params = new URLSearchParams("a=1"); + const name = { toString: function(){ return "a"; } }; + expect(params.get(name)).toBe("1"); + expect(params.has(name)).toBe(true); + params.delete(name); + expect(params.has("a")).toBe(false); + }); + + it("Test URLSearchParams throws when the name is a Symbol", function(){ + const params = new URLSearchParams("a=1"); + expect(function(){ params.get(Symbol("x")); }).toThrow(); + expect(function(){ params.getAll(Symbol("x")); }).toThrow(); + expect(function(){ params.has(Symbol("x")); }).toThrow(); + expect(function(){ params.delete(Symbol("x")); }).toThrow(); + expect(params.get("a")).toBe("1"); + }); + + it("Test URLSearchParams coerces non-string arguments in append and set", function(){ + const params = new URLSearchParams(); + params.append(1, 2); + expect(params.get("1")).toBe("2"); + params.set(true, { toString: function(){ return "x"; } }); + expect(params.get("true")).toBe("x"); + params.append("a", null); + expect(params.get("a")).toBe("null"); + }); + + it("Test URLSearchParams append and set throw when an argument is a Symbol", function(){ + const params = new URLSearchParams("a=1"); + expect(function(){ params.append(Symbol("x"), "v"); }).toThrow(); + expect(function(){ params.append("k", Symbol("x")); }).toThrow(); + expect(function(){ params.set(Symbol("x"), "v"); }).toThrow(); + expect(function(){ params.set("k", Symbol("x")); }).toThrow(); + // Nothing was appended or replaced by the failed calls. + expect(params.toString()).toBe("a=1"); + }); + + // --- Brand checks: iterators require a genuine receiver. --- + + it("Test URLSearchParams entries/keys/values throw on a foreign receiver", function(){ + const params = new URLSearchParams("a=1"); + expect(function(){ params.entries.call({}); }).toThrow(); + expect(function(){ params.keys.call({}); }).toThrow(); + expect(function(){ params.values.call({}); }).toThrow(); + }); + + it("Test URLSearchParams methods throw on a foreign receiver", function(){ + const params = new URLSearchParams("a=1"); + expect(function(){ params.get.call({}, "a"); }).toThrow(); + expect(function(){ params.getAll.call({}, "a"); }).toThrow(); + expect(function(){ params.has.call({}, "a"); }).toThrow(); + expect(function(){ params.append.call({}, "a", "b"); }).toThrow(); + expect(function(){ params.set.call({}, "a", "b"); }).toThrow(); + expect(function(){ params.delete.call({}, "a"); }).toThrow(); + expect(function(){ params.forEach.call({}, function(){}); }).toThrow(); + expect(function(){ params.sort.call({}); }).toThrow(); + expect(function(){ params.toString.call({}); }).toThrow(); + }); + + it("Test URLSearchParams iterator next() throws on a foreign receiver", function(){ + const params = new URLSearchParams("a=1"); + const iterator = params.entries(); + const next = iterator.next; + expect(function(){ next.call({}); }).toThrow(); + // The iterator keeps working when invoked correctly afterwards. + const first = iterator.next(); + expect(first.done).toBe(false); + expect(first.value[0]).toBe("a"); + }); + + it("Test URLSearchParams entries retargets to another instance receiver", function(){ + const a = new URLSearchParams("a=1"); + const b = new URLSearchParams("b=2"); + const entries = [...a.entries.call(b)]; + expect(entries.length).toBe(1); + expect(entries[0][0]).toBe("b"); + expect(entries[0][1]).toBe("2"); + }); + }); diff --git a/test-app/app/src/main/java/com/tns/AndroidJsV8Inspector.java b/test-app/app/src/main/java/com/tns/AndroidJsV8Inspector.java index 28c15646a..fd1ef7329 100644 --- a/test-app/app/src/main/java/com/tns/AndroidJsV8Inspector.java +++ b/test-app/app/src/main/java/com/tns/AndroidJsV8Inspector.java @@ -37,6 +37,8 @@ class AndroidJsV8Inspector { protected native final void dispatchMessage(String message); + private native String handleMessageOnSocketThread(String message); + private Handler mainHandler; private final Object debugBrkLock; @@ -294,6 +296,27 @@ protected void onMessage(final NanoWSD.WebSocketFrame message) { Log.d("V8Inspector", "To dbg backend: " + message.getTextPayload() + " ThreadId:" + Thread.currentThread().getId()); } + // Network.loadNetworkResource / IO.read / IO.close are served from + // disk on this thread so source maps load even while the isolate is + // paused at a breakpoint or busy running JS; Target domain commands + // and worker-session messages (top-level sessionId) are routed here + // too. Debugger.pause schedules a V8 interrupt and still flows + // through the queue. A null return means "not handled" (queue it); + // an empty string means handled with nothing left to send. + String fastPathResponse = handleMessageOnSocketThread(message.getTextPayload()); + if (fastPathResponse != null) { + if (!fastPathResponse.isEmpty()) { + try { + send(fastPathResponse); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + return; + } + inspectorMessages.offer(message.getTextPayload()); if (!AndroidJsV8Inspector.ReadyToProcessMessages.get()) { diff --git a/test-app/app/src/main/java/com/tns/tests/ByteBufferHolder.java b/test-app/app/src/main/java/com/tns/tests/ByteBufferHolder.java new file mode 100644 index 000000000..8baa46e41 --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/ByteBufferHolder.java @@ -0,0 +1,24 @@ +package com.tns.tests; + +import java.nio.ByteBuffer; + +/* + * Used by shared-array-buffer-test.js to verify that JS (Shared)ArrayBuffers + * marshal to Java as direct ByteBuffers over the same memory. + */ +public class ByteBufferHolder { + private ByteBuffer buffer; + + public ByteBuffer hold(ByteBuffer buffer) { + this.buffer = buffer; + return this.buffer; + } + + public byte get(int index) { + return buffer.get(index); + } + + public void put(int index, byte value) { + buffer.put(index, value); + } +} diff --git a/test-app/build-tools/jsparser/visitors/es5-visitors.js b/test-app/build-tools/jsparser/visitors/es5-visitors.js index f78d1d9ae..c857bff40 100644 --- a/test-app/build-tools/jsparser/visitors/es5-visitors.js +++ b/test-app/build-tools/jsparser/visitors/es5-visitors.js @@ -526,13 +526,32 @@ var es5_visitors = (function() { /* * HELPER METHODS */ + + // Returns the Identifier name of a property's key, or null when the property + // is a SpreadElement, has a computed key, or uses a non-Identifier key + // (e.g. StringLiteral, NumericLiteral). Such properties cannot be mapped to + // a Java method binding, so callers should skip them. + function _getIdentifierKeyName(property) { + if (!property || property.computed) { + return null; + } + if (!property.key || !types.isIdentifier(property.key)) { + return null; + } + return property.key.name; + } + function _getOverriddenMethods(node, config) { var overriddenMethodNames = []; if (types.isObjectExpression(node)) { var objectProperties = node.properties; for (var index in objectProperties) { - overriddenMethodNames.push(objectProperties[index].key.name); + var keyName = _getIdentifierKeyName(objectProperties[index]); + if (keyName === null) { + continue; + } + overriddenMethodNames.push(keyName); } } @@ -567,9 +586,18 @@ var es5_visitors = (function() { will get 'method1' and 'method3' */ for (var index in objectProperties) { + var keyName = _getIdentifierKeyName(objectProperties[index]); + // Skip spreads, computed keys, and non-Identifier keys — they + // cannot be statically mapped to a Java binding. Without this + // guard, valid (non-NS) `.extend({ ...other, foo })` calls in + // bundled vendor code (e.g. Zod schemas) would crash the parser. + if (keyName === null) { + continue; + } + // if the user has declared interfaces that he is implementing if (!interfacesFound && - objectProperties[index].key.name.toLowerCase() === "interfaces" && + keyName.toLowerCase() === "interfaces" && types.isArrayExpression(objectProperties[index].value)) { interfacesFound = true; var interfaces = objectProperties[index].value.elements; @@ -579,7 +607,7 @@ var es5_visitors = (function() { implementedInterfaces.push(interfaceName); } } else { - overriddenMethodNames.push(objectProperties[index].key.name) + overriddenMethodNames.push(keyName) } } } diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index 2eaeaa7ee..a8e428801 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -74,6 +74,7 @@ if (NOT OPTIMIZED_BUILD OR OPTIMIZED_WITH_INSPECTOR_BUILD) src/main/cpp/com_tns_AndroidJsV8Inspector.cpp src/main/cpp/JsV8InspectorClient.cpp + src/main/cpp/WorkerInspectorClient.cpp src/main/cpp/v8_inspector/ns-v8-tracing-agent-impl.cpp src/main/cpp/v8_inspector/Utils.cpp ) @@ -99,6 +100,7 @@ add_library( src/main/cpp/ArrayHelper.cpp src/main/cpp/AssetExtractor.cpp src/main/cpp/CallbackHandlers.cpp + src/main/cpp/ConcurrentQueue.cpp src/main/cpp/Constants.cpp src/main/cpp/DirectBuffer.cpp src/main/cpp/FieldAccessor.cpp @@ -112,6 +114,7 @@ add_library( src/main/cpp/JsArgToArrayConverter.cpp src/main/cpp/JSONObjectHelper.cpp src/main/cpp/Logger.cpp + src/main/cpp/LooperTasks.cpp src/main/cpp/ManualInstrumentation.cpp src/main/cpp/MessageLoopTimer.cpp src/main/cpp/MetadataMethodInfo.cpp @@ -135,6 +138,8 @@ add_library( src/main/cpp/V8GlobalHelpers.cpp src/main/cpp/V8StringConstants.cpp src/main/cpp/WeakRef.cpp + src/main/cpp/WorkerMessage.cpp + src/main/cpp/WorkerWrapper.cpp src/main/cpp/Timers.cpp src/main/cpp/com_tns_AssetExtractor.cpp src/main/cpp/com_tns_Runtime.cpp diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index 212407497..f48b814d7 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -87,7 +87,7 @@ android { } if (hasNdkVersion) { - ndkVersion ndkVersion + ndkVersion project.ndkVersion } else { ndkVersion defaultNdkVersion } diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp index 709359a76..5ce450fef 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp @@ -17,6 +17,8 @@ #include "MethodCache.h" #include "SimpleProfiler.h" #include "Runtime.h" +#include "WorkerMessage.h" +#include "WorkerWrapper.h" #include #include @@ -56,11 +58,6 @@ void CallbackHandlers::Init(Isolate *isolate) { "()V"); assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); - INIT_WORKER_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "initWorker", - "(Ljava/lang/String;Ljava/lang/String;I)V"); - - assert(INIT_WORKER_METHOD_ID != nullptr); - MetadataNode::Init(isolate); MethodCache::Init(); @@ -989,6 +986,72 @@ jobjectArray CallbackHandlers::GetJavaStringArray(JEnv &env, int length) { return (jobjectArray) env.NewGlobalRef(tmpArr); } +/* + * Resolves the `androidPriority` Worker option to an android.os.Process + * thread priority (nice value). Accepts the THREAD_PRIORITY_* names in + * camelCase or a raw nice value clamped to [-20, 19]. + * Defaults to THREAD_PRIORITY_BACKGROUND (10), the previously hardcoded value. + */ +static int GetWorkerThreadPriority(Isolate *isolate, Local context, + const v8::FunctionCallbackInfo &args) { + const int defaultPriority = 10; // android.os.Process.THREAD_PRIORITY_BACKGROUND + + if (args.Length() < 2 || !args[1]->IsObject()) { + return defaultPriority; + } + + auto options = args[1].As(); + Local value; + if (!options->Get(context, ArgConverter::ConvertToV8String(isolate, "androidPriority")) + .ToLocal(&value) || + value->IsNullOrUndefined()) { + return defaultPriority; + } + + if (value->IsNumber()) { + int priority = value->Int32Value(context).FromMaybe(defaultPriority); + if (priority < -20) { + priority = -20; + } else if (priority > 19) { + priority = 19; + } + return priority; + } + + if (value->IsString()) { + auto name = ArgConverter::ConvertToString(value.As()); + if (name == "lowest") { + return 19; + } else if (name == "background") { + return 10; + } else if (name == "lessFavorable") { + return 1; + } else if (name == "default") { + return 0; + } else if (name == "moreFavorable") { + return -1; + } else if (name == "foreground") { + return -2; + } else if (name == "display") { + return -4; + } else if (name == "urgentDisplay") { + return -8; + } else if (name == "video") { + return -10; + } else if (name == "audio") { + return -16; + } else if (name == "urgentAudio") { + return -19; + } + } + + throw NativeScriptException( + "Invalid value for the Worker 'androidPriority' option. Expected one of: " + "'lowest', 'background', 'lessFavorable', 'default', 'moreFavorable', " + "'foreground', 'display', 'urgentDisplay', 'video', 'audio', 'urgentAudio' " + "or a number between -20 and 19."); +} + void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfo &args) { try { if (!args.IsConstructCall()) { @@ -1043,8 +1106,8 @@ void CallbackHandlers::NewThreadCallback(const v8::FunctionCallbackInfoGetFrame( - isolate, 0)->GetScriptName(); - auto currentExecutingScriptNameStr = ArgConverter::ConvertToString( - currentExecutingScriptName.As()); - auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); - auto currentDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); - string fileSchema("file://"); - if (currentDir.compare(0, fileSchema.length(), fileSchema) == 0) { - currentDir = currentDir.substr(fileSchema.length()); + /* + * Relative worker paths are resolved against the calling module's + * directory. The caller may have no script name (e.g. eval'd code) or + * the script may not be found there - in both cases fall back to + * app-root-relative resolution, mirroring the iOS runtime. + */ + std::string currentDir = Constants::APP_ROOT_FOLDER_PATH; + auto stack = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName); + if (!stack.IsEmpty() && stack->GetFrameCount() > 0) { + auto currentExecutingScriptName = stack->GetFrame(isolate, 0)->GetScriptName(); + auto currentExecutingScriptNameStr = ArgConverter::ConvertToString( + currentExecutingScriptName); + auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); + if (lastForwardSlash != std::string::npos) { + auto callerDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); + string fileSchema("file://"); + if (callerDir.compare(0, fileSchema.length(), fileSchema) == 0) { + callerDir = callerDir.substr(fileSchema.length()); + } + currentDir = callerDir; + } } - // Will throw if path is invalid or doesn't exist - ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir); + // Will throw if the path is invalid or the file doesn't exist + try { + ModuleInternal::CheckFileExists(isolate, resolvedPath, currentDir); + } catch (NativeScriptException& e) { + if (currentDir == Constants::APP_ROOT_FOLDER_PATH) { + throw; + } + // not found next to the caller - retry against the app root + ModuleInternal::CheckFileExists(isolate, resolvedPath, + Constants::APP_ROOT_FOLDER_PATH); + currentDir = Constants::APP_ROOT_FOLDER_PATH; + } - auto workerId = nextWorkerId++; + auto workerId = WorkerWrapper::NextWorkerId(); V8SetPrivateValue(isolate, thiz, ArgConverter::ConvertToV8String(isolate, "workerId"), Number::New(isolate, workerId)); - auto persistentWorker = new Persistent(isolate, thiz); + // Resolve the jclass/jmethodID handles the worker thread will need, + // here on the main thread where class loading is safe. + WorkerWrapper::EnsureJniCached(); - id2WorkerMap.emplace(workerId, persistentWorker); + auto wrapper = std::make_shared(isolate, workerId, resolvedPath, + currentDir, priority, thiz); + WorkerWrapper::Insert(workerId, wrapper); DEBUG_WRITE("Called Worker constructor id=%d", workerId); - JEnv env; - Local filePathStr = ArgConverter::ConvertToV8String(isolate, resolvedPath); - JniLocalRef filePath(ArgConverter::ConvertToJavaString(filePathStr)); - JniLocalRef dirPath(env.NewStringUTF(currentDir.c_str())); - - env.CallStaticVoidMethod(RUNTIME_CLASS, INIT_WORKER_METHOD_ID, (jstring) filePath, - (jstring) dirPath, workerId); + wrapper->Start(); } catch (NativeScriptException &e) { e.ReThrowToV8(); } catch (std::exception e) { @@ -1122,20 +1204,25 @@ CallbackHandlers::WorkerObjectPostMessageCallback(const v8::FunctionCallbackInfo jsId); auto context = isolate->GetCurrentContext(); - auto objToStringify = args[0]->ToObject(context).ToLocalChecked(); - std::string msg = tns::JsonStringifyObject(isolate, objToStringify, false); - // get worker's ID that is associated on the other side - in Java + // get worker's ID that is associated with the WorkerWrapper auto id = jsId->Int32Value(context).ToChecked(); - JEnv env; - auto mId = env.GetStaticMethodID(RUNTIME_CLASS, "sendMessageFromMainToWorker", - "(ILjava/lang/String;)V"); + auto wrapper = WorkerWrapper::GetById(id); + if (wrapper == nullptr || wrapper->IsTerminating() || wrapper->IsClosing()) { + DEBUG_WRITE( + "MAIN: WorkerObjectPostMessageCallback - worker(id=%d) is terminated or closing. No message will be sent.", + id); + return; + } - jstring jmsg = env.NewStringUTF(msg.c_str()); - JniLocalRef jmsgRef(jmsg); + auto message = std::make_shared(); + if (message->Serialize(isolate, context, args[0]).IsNothing()) { + // a DataCloneError is already pending on the isolate + return; + } - env.CallStaticVoidMethod(RUNTIME_CLASS, mId, id, (jstring) jmsgRef); + wrapper->PostMessage(message); DEBUG_WRITE( "MAIN: WorkerObjectPostMessageCallback called postMessage on Worker object(id=%d)", @@ -1153,55 +1240,6 @@ CallbackHandlers::WorkerObjectPostMessageCallback(const v8::FunctionCallbackInfo } } -void CallbackHandlers::WorkerGlobalOnMessageCallback(Isolate *isolate, jstring message) { - auto context = isolate->GetCurrentContext(); - - try { - auto globalObject = context->Global(); - - TryCatch tc(isolate); - - auto callback = globalObject->Get(context, ArgConverter::ConvertToV8String(isolate, - "onmessage")).ToLocalChecked(); - auto isEmpty = callback.IsEmpty(); - auto isFunction = callback->IsFunction(); - - if (!isEmpty && isFunction) { - auto msgString = ArgConverter::jstringToV8String(isolate, message).As(); - Local msg; - JSON::Parse(context, msgString).ToLocal(&msg); - - auto obj = Object::New(isolate); - obj->DefineOwnProperty(isolate->GetCurrentContext(), - ArgConverter::ConvertToV8String(isolate, "data"), msg, - PropertyAttribute::ReadOnly); - Local args1[] = {obj}; - - auto func = callback.As(); - - func->Call(context, Undefined(isolate), 1, args1); - } else { - DEBUG_WRITE( - "WORKER: WorkerGlobalOnMessageCallback couldn't fire a worker's `onmessage` callback because it isn't implemented!"); - } - - if (tc.HasCaught()) { - // TODO: Pete: Will catch exceptions thrown artificially in postMessage callbacks inside of 'onmessage' implementation - CallWorkerScopeOnErrorHandle(isolate, tc); - } - } catch (NativeScriptException &ex) { - ex.ReThrowToV8(); - } catch (std::exception e) { - stringstream ss; - ss << "Error: c++ exception: " << e.what() << endl; - NativeScriptException nsEx(ss.str()); - nsEx.ReThrowToV8(); - } catch (...) { - NativeScriptException nsEx(std::string("Error: c++ exception!")); - nsEx.ReThrowToV8(); - } -} - void CallbackHandlers::WorkerGlobalPostMessageCallback(const v8::FunctionCallbackInfo &args) { auto isolate = args.GetIsolate(); @@ -1222,83 +1260,23 @@ CallbackHandlers::WorkerGlobalPostMessageCallback(const v8::FunctionCallbackInfo return; } - auto context = isolate->GetCurrentContext(); - auto objToStringify = args[0]->ToObject(context).ToLocalChecked(); - std::string msg = tns::JsonStringifyObject(isolate, objToStringify, false); - - JEnv env; - auto mId = env.GetStaticMethodID(RUNTIME_CLASS, "sendMessageFromWorkerToMain", - "(Ljava/lang/String;)V"); - - auto jmsg = env.NewStringUTF(msg.c_str()); - JniLocalRef jmsgRef(jmsg); - - env.CallStaticVoidMethod(RUNTIME_CLASS, mId, (jstring) jmsgRef); - - DEBUG_WRITE("WORKER: WorkerGlobalPostMessageCallback called."); - } catch (NativeScriptException &ex) { - ex.ReThrowToV8(); - } catch (std::exception e) { - stringstream ss; - ss << "Error: c++ exception: " << e.what() << endl; - NativeScriptException nsEx(ss.str()); - nsEx.ReThrowToV8(); - } catch (...) { - NativeScriptException nsEx(std::string("Error: c++ exception!")); - nsEx.ReThrowToV8(); - } -} - -void -CallbackHandlers::WorkerObjectOnMessageCallback(Isolate *isolate, jint workerId, jstring message) { - try { - auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); - - if (workerFound == CallbackHandlers::id2WorkerMap.end()) { - // TODO: Pete: Throw exception + auto wrapper = WorkerWrapper::FromIsolate(isolate); + if (wrapper == nullptr || wrapper->IsTerminating()) { DEBUG_WRITE( - "MAIN: WorkerObjectOnMessageCallback no worker instance was found with workerId=%d.", - workerId); + "WORKER: WorkerGlobalPostMessageCallback - worker is terminating. No message will be sent."); return; } - auto workerPersistent = workerFound->second; - - if (workerPersistent->IsEmpty()) {// Object has been collected - DEBUG_WRITE( - "MAIN: WorkerObjectOnMessageCallback couldn't fire a worker(id=%d) object's `onmessage` callback because the worker has been Garbage Collected.", - workerId); - CallbackHandlers::id2WorkerMap.erase(workerId); + auto context = isolate->GetCurrentContext(); + auto message = std::make_shared(); + if (message->Serialize(isolate, context, args[0]).IsNothing()) { + // a DataCloneError is already pending on the isolate return; } - auto worker = Local::New(isolate, *workerPersistent); - - auto context = isolate->GetCurrentContext(); - auto callback = worker->Get(context, ArgConverter::ConvertToV8String(isolate, - "onmessage")).ToLocalChecked(); - auto isEmpty = callback.IsEmpty(); - auto isFunction = callback->IsFunction(); - - if (!isEmpty && isFunction) { - auto msgString = ArgConverter::jstringToV8String(isolate, message).As(); - Local msg; - JSON::Parse(context, msgString).ToLocal(&msg); - - auto obj = Object::New(isolate); - obj->DefineOwnProperty(context, - ArgConverter::ConvertToV8String(isolate, "data"), msg, - PropertyAttribute::ReadOnly); - Local args1[] = {obj}; + wrapper->PostMessageToParent(message); - auto func = callback.As(); - - func->Call(context, Undefined(isolate), 1, args1); - } else { - DEBUG_WRITE( - "MAIN: WorkerObjectOnMessageCallback couldn't fire a worker(id=%d) object's `onmessage` callback because it isn't implemented.", - workerId); - } + DEBUG_WRITE("WORKER: WorkerGlobalPostMessageCallback called."); } catch (NativeScriptException &ex) { ex.ReThrowToV8(); } catch (std::exception e) { @@ -1347,14 +1325,13 @@ CallbackHandlers::WorkerObjectTerminateCallback(const v8::FunctionCallbackInfoTerminate(); + } - // Remove persistent handle from id2WorkerMap - CallbackHandlers::ClearWorkerPersistent(id); + // Reset the persistent Worker object handle and drop the registry entry + WorkerWrapper::ClearWorkerOnParent(id); } catch (NativeScriptException &ex) { ex.ReThrowToV8(); } catch (std::exception e) { @@ -1414,11 +1391,10 @@ void CallbackHandlers::WorkerGlobalCloseCallback(const v8::FunctionCallbackInfo< CallWorkerScopeOnErrorHandle(isolate, tc); } - JEnv env; - auto mId = env.GetStaticMethodID(RUNTIME_CLASS, "workerScopeClose", - "()V"); - - env.CallStaticVoidMethod(RUNTIME_CLASS, mId); + auto wrapper = WorkerWrapper::FromIsolate(isolate); + if (wrapper != nullptr) { + wrapper->Close(); + } } catch (NativeScriptException &ex) { ex.ReThrowToV8(); } catch (std::exception e) { @@ -1432,8 +1408,54 @@ void CallbackHandlers::WorkerGlobalCloseCallback(const v8::FunctionCallbackInfo< } } +/* + * Extracts message/filename/stack/line info from a TryCatch into plain + * strings that can safely cross to the main thread. Robust against empty + * v8 messages (e.g. when execution was terminated). + */ +static void ExtractTryCatchInfo(Isolate *isolate, Local context, TryCatch &tc, + std::string &message, std::string &source, + std::string &stackTrace, int &lineno) { + message = ""; + source = ""; + stackTrace = ""; + lineno = 0; + + if (!tc.Message().IsEmpty()) { + lineno = tc.Message()->GetLineNumber(context).FromMaybe(0); + message = ArgConverter::ConvertToString(tc.Message()->Get()); + Local src; + if (tc.Message()->GetScriptResourceName()->ToString(context).ToLocal(&src)) { + source = ArgConverter::ConvertToString(src); + } + } + + if (message.empty() && !tc.Exception().IsEmpty()) { + Local exStr; + if (tc.Exception()->ToDetailString(context).ToLocal(&exStr)) { + message = ArgConverter::ConvertToString(exStr); + } + } + + Local outStackTrace = tc.StackTrace(context).FromMaybe(Local()); + if (!outStackTrace.IsEmpty()) { + Local stackTraceStr = + outStackTrace->ToDetailString(context).FromMaybe(Local()); + if (!stackTraceStr.IsEmpty()) { + stackTrace = ArgConverter::ConvertToString(stackTraceStr); + } + } +} + void CallbackHandlers::CallWorkerScopeOnErrorHandle(Isolate *isolate, TryCatch &tc) { try { + auto wrapper = WorkerWrapper::FromIsolate(isolate); + if (wrapper != nullptr && wrapper->IsTerminating()) { + // The worker was terminated mid-execution (e.g. terminate() + // interrupting a busy loop) - nothing to report. + return; + } + TryCatch innerTc(isolate); // See if `onerror` handle is implemented @@ -1446,7 +1468,7 @@ void CallbackHandlers::CallWorkerScopeOnErrorHandle(Isolate *isolate, TryCatch & auto isEmpty = callback.IsEmpty(); auto isFunction = callback->IsFunction(); - if (!isEmpty && isFunction) { + if (!isEmpty && isFunction && !tc.Message().IsEmpty()) { auto msg = tc.Message()->Get(); Local args1[] = {msg}; @@ -1462,125 +1484,22 @@ void CallbackHandlers::CallWorkerScopeOnErrorHandle(Isolate *isolate, TryCatch & } } - // will account for exceptions thrown inside the error handler - if (innerTc.HasCaught()) { - auto lno = innerTc.Message()->GetLineNumber(context).ToChecked(); - auto msg = innerTc.Message()->Get(); - Local outStackTrace = innerTc.StackTrace(context).FromMaybe(Local()); - Local stackTrace; - if (!outStackTrace.IsEmpty()) { - stackTrace = outStackTrace->ToDetailString(context).FromMaybe(Local()); - } - auto source = innerTc.Message()->GetScriptResourceName()->ToString( - context).ToLocalChecked(); - - auto runtime = Runtime::GetRuntime(isolate); - runtime->PassUncaughtExceptionFromWorkerToMainHandler(msg, stackTrace, source, lno); - } - - // throw so that it may bubble up to main - auto lno = tc.Message()->GetLineNumber(context).ToChecked(); - auto msg = tc.Message()->Get(); - auto source = tc.Message()->GetScriptResourceName()->ToString(context).ToLocalChecked(); - Local outStackTrace = tc.StackTrace(context).FromMaybe(Local()); - Local stackTrace; - if (!outStackTrace.IsEmpty()) { - stackTrace = outStackTrace->ToDetailString(context).FromMaybe(Local()); - } - - auto runtime = Runtime::GetRuntime(isolate); - runtime->PassUncaughtExceptionFromWorkerToMainHandler(msg, stackTrace, source, lno); - } catch (NativeScriptException &ex) { - ex.ReThrowToV8(); - } catch (std::exception e) { - stringstream ss; - ss << "Error: c++ exception: " << e.what() << endl; - NativeScriptException nsEx(ss.str()); - nsEx.ReThrowToV8(); - } catch (...) { - NativeScriptException nsEx(std::string("Error: c++ exception!")); - nsEx.ReThrowToV8(); - } -} - -void -CallbackHandlers::CallWorkerObjectOnErrorHandle(Isolate *isolate, jint workerId, jstring message, - jstring stackTrace, jstring filename, jint lineno, - jstring threadName) { - try { - auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); - - if (workerFound == CallbackHandlers::id2WorkerMap.end()) { - // TODO: Pete: Throw exception - DEBUG_WRITE( - "MAIN: CallWorkerObjectOnErrorHandle no worker instance was found with workerId=%d.", - workerId); + if (wrapper == nullptr) { return; } - auto workerPersistent = workerFound->second; - - if (workerPersistent->IsEmpty()) {// Object has been collected - DEBUG_WRITE( - "MAIN: WorkerObjectOnMessageCallback couldn't fire a worker(id=%d) object's `onmessage` callback because the worker has been Garbage Collected.", - workerId); - CallbackHandlers::id2WorkerMap.erase(workerId); - return; - } - - auto worker = Local::New(isolate, *workerPersistent); - - auto context = isolate->GetCurrentContext(); - auto callback = worker->Get(context, ArgConverter::ConvertToV8String(isolate, - "onerror")).ToLocalChecked(); - auto isEmpty = callback.IsEmpty(); - auto isFunction = callback->IsFunction(); - - if (!isEmpty && isFunction) { - auto errEvent = Object::New(isolate); - errEvent->Set(context, - ArgConverter::ConvertToV8String(isolate, "message"), - ArgConverter::jstringToV8String(isolate, message)); - errEvent->Set(context, - ArgConverter::ConvertToV8String(isolate, "stackTrace"), - ArgConverter::jstringToV8String(isolate, stackTrace)); - errEvent->Set(context, - ArgConverter::ConvertToV8String(isolate, "filename"), - ArgConverter::jstringToV8String(isolate, filename)); - errEvent->Set(context, - ArgConverter::ConvertToV8String(isolate, "lineno"), - Number::New(isolate, lineno)); - - Local args1[] = {errEvent}; + std::string message, source, stackTrace; + int lineno; - auto func = callback.As(); - - // Handle exceptions thrown in onmessage with the worker.onerror handler, if present - Local result; - func->Call(context, Undefined(isolate), 1, args1).ToLocal(&result); - if (!result.IsEmpty() && result->BooleanValue(isolate)) { - // Do nothing, exception is handled and does not need to be raised to application level - return; - } + // will account for exceptions thrown inside the error handler + if (innerTc.HasCaught()) { + ExtractTryCatchInfo(isolate, context, innerTc, message, source, stackTrace, lineno); + wrapper->PassUncaughtExceptionFromWorkerToParent(message, source, stackTrace, lineno); } - // Exception wasn't handled, or is critical -> Throw exception - auto strMessage = ArgConverter::jstringToString(message); - auto strFilename = ArgConverter::jstringToString(filename); - auto strThreadname = ArgConverter::jstringToString(threadName); - auto strStackTrace = ArgConverter::jstringToString(stackTrace); - - DEBUG_WRITE( - "Unhandled exception in '%s' thread. file: %s, line %d, message: %s\nStackTrace: %s", - strThreadname.c_str(), strFilename.c_str(), lineno, strMessage.c_str(), - strStackTrace.c_str()); - - // Do not throw exception? -// stringstream ss; -// ss << endl << "Unhandled exception in '" << strThreadname << "' thread. file: " << strFilename << -// ", line: " << lineno << endl << strMessage << endl; -// NativeScriptException ex(ss.str()); -// throw ex; + // bubble up to the main thread's Worker object `onerror` + ExtractTryCatchInfo(isolate, context, tc, message, source, stackTrace, lineno); + wrapper->PassUncaughtExceptionFromWorkerToParent(message, source, stackTrace, lineno); } catch (NativeScriptException &ex) { ex.ReThrowToV8(); } catch (std::exception e) { @@ -1594,28 +1513,6 @@ CallbackHandlers::CallWorkerObjectOnErrorHandle(Isolate *isolate, jint workerId, } } -void CallbackHandlers::ClearWorkerPersistent(int workerId) { - DEBUG_WRITE("ClearWorkerPersistent called for workerId=%d", workerId); - - auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); - - if (workerFound == CallbackHandlers::id2WorkerMap.end()) { - DEBUG_WRITE( - "MAIN | WORKER: ClearWorkerPersistent no worker instance was found with workerId=%d ! The worker may already be terminated.", - workerId); - return; - } - - auto workerPersistent = workerFound->second; - workerPersistent->Reset(); - - id2WorkerMap.erase(workerId); -} - -void CallbackHandlers::TerminateWorkerThread(Isolate *isolate) { - isolate->TerminateExecution(); -} - void CallbackHandlers::RemoveIsolateEntries(v8::Isolate *isolate) { for (auto &item: cache_) { if (item.second.isolate_ == isolate) { @@ -1791,9 +1688,6 @@ std::atomic_int64_t CallbackHandlers::count_ = {0}; std::atomic_uint64_t CallbackHandlers::frameCallbackCount_ = {0}; -int CallbackHandlers::nextWorkerId = 0; -robin_hood::unordered_map *> CallbackHandlers::id2WorkerMap; - short CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH = 100; jclass CallbackHandlers::RUNTIME_CLASS = nullptr; jclass CallbackHandlers::JAVA_LANG_STRING = nullptr; @@ -1803,7 +1697,6 @@ jmethodID CallbackHandlers::MAKE_INSTANCE_STRONG_ID = nullptr; jmethodID CallbackHandlers::GET_TYPE_METADATA = nullptr; jmethodID CallbackHandlers::ENABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; jmethodID CallbackHandlers::DISABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; -jmethodID CallbackHandlers::INIT_WORKER_METHOD_ID = nullptr; NumericCasts CallbackHandlers::castFunctions; diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.h b/test-app/runtime/src/main/cpp/CallbackHandlers.h index d079d4730..eddcca93d 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.h +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.h @@ -24,14 +24,6 @@ namespace tns { class CallbackHandlers { public: - /* - * Stores persistent handles of all 'Worker' objects initialized on the main thread - * Note: No isolates different than that of the main thread should access this map - */ - static robin_hood::unordered_map *> id2WorkerMap; - - static int nextWorkerId; - static void Init(v8::Isolate *isolate); static v8::Local @@ -130,34 +122,22 @@ namespace tns { static void NewThreadCallback(const v8::FunctionCallbackInfo &args); /* - * main -> worker messaging - * Fired when a Worker instance's postMessage is called + * parent -> worker messaging + * Fired when a Worker instance's postMessage is called. + * Serializes the payload (structured clone) and queues it on the + * worker's C++ inbox. */ static void WorkerObjectPostMessageCallback(const v8::FunctionCallbackInfo &args); /* - * main -> worker messaging - * Fired when worker object has "postMessage" and the worker has implemented "onMessage" handler - * In case "onMessage" handler isn't implemented no exception is thrown - */ - static void WorkerGlobalOnMessageCallback(v8::Isolate *isolate, jstring message); - - /* - * worker -> main thread messaging - * Fired when a Worker script's "postMessage" is called + * worker -> parent messaging + * Fired when a Worker script's "postMessage" is called. + * Serializes the payload and posts it to the parent runtime's task queue. */ static void WorkerGlobalPostMessageCallback(const v8::FunctionCallbackInfo &args); - /* - * worker -> main messaging - * Fired when worker has sent a message to main and the worker object has implemented "onMessage" handler - * In case "onMessage" handler isn't implemented no exception is thrown - */ - static void - WorkerObjectOnMessageCallback(v8::Isolate *isolate, jint workerId, jstring message); - /* * Fired when a Worker instance's terminate is called (immediately stops execution of the thread) */ @@ -168,17 +148,6 @@ namespace tns { */ static void WorkerGlobalCloseCallback(const v8::FunctionCallbackInfo &args); - /* - * Clears the persistent Worker object handle associated with a workerId - * Occurs when calling a worker object's `terminate` or a worker thread's global scope `close` - */ - static void ClearWorkerPersistent(int workerId); - - /* - * Terminates the currently executing Isolate. No scripts can be executed after this call - */ - static void TerminateWorkerThread(v8::Isolate *isolate); - /* * Is called when an unhandled exception is thrown inside the worker * Will execute 'onerror' if one is provided inside the Worker Scope @@ -187,16 +156,6 @@ namespace tns { */ static void CallWorkerScopeOnErrorHandle(v8::Isolate *isolate, v8::TryCatch &tc); - /* - * Is called when an unhandled exception bubbles up from the worker scope to the main thread Worker Object - * Will execute `onerror` if one is implemented for the Worker Object instance - * Will throw a NativeScript Exception if 'onerror' isn't implemented or returns false - */ - static void - CallWorkerObjectOnErrorHandle(v8::Isolate *isolate, jint workerId, jstring message, - jstring stackTrace, jstring filename, jint lineno, - jstring threadName); - static void RemoveIsolateEntries(v8::Isolate *isolate); @@ -261,8 +220,6 @@ namespace tns { static jmethodID DISABLE_VERBOSE_LOGGING_METHOD_ID; - static jmethodID INIT_WORKER_METHOD_ID; - static NumericCasts castFunctions; static ArrayElementAccessor arrayElementAccessor; diff --git a/test-app/runtime/src/main/cpp/ConcurrentQueue.cpp b/test-app/runtime/src/main/cpp/ConcurrentQueue.cpp new file mode 100644 index 000000000..cc43b238c --- /dev/null +++ b/test-app/runtime/src/main/cpp/ConcurrentQueue.cpp @@ -0,0 +1,99 @@ +#include "ConcurrentQueue.h" + +#include +#include + +#include +#include + +#include "NativeScriptAssert.h" + +namespace tns { + +void ConcurrentQueue::Initialize(ALooper* looper, ALooper_callbackFunc performWork, + void* data) { + std::unique_lock lock(initializationMutex_); + if (terminated_) { + return; + } + + int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd == -1) { + DEBUG_WRITE_FORCE("ConcurrentQueue: eventfd failed: %s", strerror(errno)); + return; + } + + if (ALooper_addFd(looper, fd, ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, + performWork, data) != 1) { + DEBUG_WRITE_FORCE("ConcurrentQueue: ALooper_addFd failed"); + close(fd); + return; + } + + this->looper_ = looper; + ALooper_acquire(this->looper_); + this->fd_ = fd; +} + +void ConcurrentQueue::Push(std::shared_ptr message) { + // The lifecycle lock is held across the enqueue + wakeup so a concurrent + // Terminate() can never leave a message stranded in a queue nothing will + // ever drain. + std::unique_lock lock(initializationMutex_); + if (terminated_) { + // the consumer is gone - drop the message (and its backing stores) + return; + } + + { + std::unique_lock mlock(this->mutex_); + this->messagesQueue_.push(message); + } + + if (this->fd_ != -1) { + // The eventfd counter coalesces multiple signals into one wakeup, + // which is fine because the drain callback uses PopAll(). + uint64_t value = 1; + write(this->fd_, &value, sizeof(value)); + } +} + +std::vector> ConcurrentQueue::PopAll() { + std::unique_lock mlock(this->mutex_); + std::vector> messages; + + while (!this->messagesQueue_.empty()) { + messages.push_back(this->messagesQueue_.front()); + this->messagesQueue_.pop(); + } + + return messages; +} + +void ConcurrentQueue::Terminate() { + // Must run on the looper's own thread: removing an fd concurrently with an + // in-flight callback dispatch is racy. + std::unique_lock lock(initializationMutex_); + terminated_ = true; + + if (this->fd_ != -1) { + ALooper_removeFd(this->looper_, this->fd_); + close(this->fd_); + this->fd_ = -1; + } + + if (this->looper_ != nullptr) { + ALooper_release(this->looper_); + this->looper_ = nullptr; + } + + // Release anything a racing Push() enqueued before it observed + // terminated_ - nothing will drain the queue from here on. + { + std::unique_lock mlock(this->mutex_); + std::queue> empty; + this->messagesQueue_.swap(empty); + } +} + +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/ConcurrentQueue.h b/test-app/runtime/src/main/cpp/ConcurrentQueue.h new file mode 100644 index 000000000..33526f443 --- /dev/null +++ b/test-app/runtime/src/main/cpp/ConcurrentQueue.h @@ -0,0 +1,38 @@ +#ifndef CONCURRENTQUEUE_H_ +#define CONCURRENTQUEUE_H_ + +#include +#include +#include +#include +#include + +#include "WorkerMessage.h" + +namespace tns { + +/* + * Thread-safe message inbox attached to an ALooper. + * Push() may be called from any thread; messages pushed before Initialize() + * are queued and can be drained explicitly once the looper is ready. + * Initialize()/PopAll()/Terminate() must be called on the looper's thread. + */ +struct ConcurrentQueue { +public: + void Initialize(ALooper* looper, ALooper_callbackFunc performWork, void* data); + void Push(std::shared_ptr message); + std::vector> PopAll(); + void Terminate(); + +private: + std::queue> messagesQueue_; + ALooper* looper_ = nullptr; + int fd_ = -1; + bool terminated_ = false; + std::mutex mutex_; + std::mutex initializationMutex_; +}; + +} // namespace tns + +#endif /* CONCURRENTQUEUE_H_ */ diff --git a/test-app/runtime/src/main/cpp/JsArgConverter.cpp b/test-app/runtime/src/main/cpp/JsArgConverter.cpp index 559ea2ef6..e5d82f0bd 100644 --- a/test-app/runtime/src/main/cpp/JsArgConverter.cpp +++ b/test-app/runtime/src/main/cpp/JsArgConverter.cpp @@ -246,7 +246,8 @@ bool JsArgConverter::ConvertArg(const Local &arg, int index) { obj = objectManager->GetJavaObjectByJsObject(jsObject); if (obj.IsNull() && (jsObject->IsTypedArray() || jsObject->IsArrayBuffer() || - jsObject->IsArrayBufferView())) { + jsObject->IsArrayBufferView() || + jsObject->IsSharedArrayBuffer())) { BufferCastType bufferCastType = tns::BufferCastType::Byte; shared_ptr store; @@ -258,6 +259,11 @@ bool JsArgConverter::ConvertArg(const Local &arg, int index) { store = array->GetBackingStore(); length = array->ByteLength(); data = static_cast(store->Data()); + } else if (jsObject->IsSharedArrayBuffer()) { + auto array = jsObject.As(); + store = array->GetBackingStore(); + length = array->ByteLength(); + data = static_cast(store->Data()); } else if (jsObject->IsArrayBufferView()) { auto array = jsObject.As(); offset = array->ByteOffset(); diff --git a/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp b/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp index 485e5efac..e7d424cbb 100644 --- a/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp +++ b/test-app/runtime/src/main/cpp/JsArgToArrayConverter.cpp @@ -249,7 +249,7 @@ bool JsArgToArrayConverter::ConvertArg(Local context, const LocalIsTypedArray() || jsObj->IsArrayBuffer() || - jsObj->IsArrayBufferView())) { + jsObj->IsArrayBufferView() || jsObj->IsSharedArrayBuffer())) { BufferCastType bufferCastType = tns::BufferCastType::Byte; shared_ptr store; @@ -261,6 +261,11 @@ bool JsArgToArrayConverter::ConvertArg(Local context, const LocalGetBackingStore(); length = array->ByteLength(); data = static_cast(store->Data()); + } else if (jsObj->IsSharedArrayBuffer()) { + auto array = jsObj.As(); + store = array->GetBackingStore(); + length = array->ByteLength(); + data = static_cast(store->Data()); } else if (jsObj->IsArrayBufferView()) { auto array = jsObj.As(); diff --git a/test-app/runtime/src/main/cpp/JsV8InspectorClient.cpp b/test-app/runtime/src/main/cpp/JsV8InspectorClient.cpp index d82ffe96c..45607bdf7 100644 --- a/test-app/runtime/src/main/cpp/JsV8InspectorClient.cpp +++ b/test-app/runtime/src/main/cpp/JsV8InspectorClient.cpp @@ -1,6 +1,9 @@ #include "JsV8InspectorClient.h" #include +#include +#include #include +#include #include #include #include @@ -12,8 +15,15 @@ #include "NativeScriptAssert.h" #include "ArgConverter.h" +#include "Constants.h" +#include "File.h" #include "Util.h" #include "Utils.h" +#include "WorkerInspectorClient.h" +#include "WorkerWrapper.h" + +#include "ada/ada.h" +#include "third_party/json.hpp" using namespace std; using namespace tns; @@ -22,6 +32,8 @@ using namespace v8; using namespace v8_inspector; using namespace inspector; +using json = nlohmann::json; + // Utility functions for converting between inspector StringView and UTF8 string static inline v8_inspector::StringView stringToStringView(const std::string &str) { @@ -29,20 +41,156 @@ static inline v8_inspector::StringView stringToStringView(const std::string &str return { chars, str.length() }; } -static inline std::string stringViewToString(v8::Isolate* isolate, const v8_inspector::StringView& stringView) { - int length = static_cast(stringView.length()); - if (!length) { - return ""; +namespace { + +// Scheme advertised to the frontend for source maps the runtime can serve. +// Chrome DevTools never loads `file:` (or `data:`/`devtools:`) resources +// through the target -- PageResourceLoader routes those to the frontend host +// machine, which cannot see files on the device. Any other scheme is fetched +// with Network.loadNetworkResource, which we answer from disk. +constexpr const char* kSourceMapScheme = "nsruntime://"; + +std::string JsonDump(const json& value) { + // replace invalid UTF-8 instead of throwing across the JNI boundary + return value.dump(-1, ' ', false, json::error_handler_t::replace); +} + +std::string FinishResponse(json& response, const std::string& sessionId) { + if (!sessionId.empty()) { + response["sessionId"] = sessionId; + } + return JsonDump(response); +} + +// Target.attachedToTarget event announcing a worker as a child target. +std::string BuildWorkerAttachedEvent(WorkerInspectorClient* client) { + json attached = {{"method", "Target.attachedToTarget"}, + {"params", + {{"sessionId", client->SessionId()}, + {"targetInfo", + {{"targetId", client->TargetId()}, + {"type", "worker"}, + {"title", client->Url()}, + {"url", client->Url()}, + {"attached", true}, + {"canAccessOpener", false}}}, + {"waitingForDebugger", false}}}}; + return JsonDump(attached); +} + +std::string Base64Encode(const char* data, size_t length) { + static const char* kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string encoded; + encoded.reserve(((length + 2) / 3) * 4); + size_t i = 0; + for (; i + 2 < length; i += 3) { + uint32_t chunk = (static_cast(data[i]) << 16) | + (static_cast(data[i + 1]) << 8) | + static_cast(data[i + 2]); + encoded.push_back(kAlphabet[(chunk >> 18) & 0x3F]); + encoded.push_back(kAlphabet[(chunk >> 12) & 0x3F]); + encoded.push_back(kAlphabet[(chunk >> 6) & 0x3F]); + encoded.push_back(kAlphabet[chunk & 0x3F]); + } + if (i < length) { + uint32_t chunk = static_cast(data[i]) << 16; + bool hasSecond = i + 1 < length; + if (hasSecond) { + chunk |= static_cast(data[i + 1]) << 8; + } + encoded.push_back(kAlphabet[(chunk >> 18) & 0x3F]); + encoded.push_back(kAlphabet[(chunk >> 12) & 0x3F]); + encoded.push_back(hasSecond ? kAlphabet[(chunk >> 6) & 0x3F] : '='); + encoded.push_back('='); + } + return encoded; +} + +// Opt-out via nativescript.config.ts (serialized into the bundled +// package.json): `android: { disableSourceMapURLRewrite: true }`, or the same +// key at the top level. +bool ShouldRewriteSourceMapURLs() { + static bool disabled = []() { + int length = 0; + auto* data = static_cast(File::ReadBinary(Constants::APP_ROOT_FOLDER_PATH + "package.json", length)); + if (data == nullptr) { + return false; + } + std::string content(reinterpret_cast(data), length); + delete[] data; + + auto parsed = json::parse(content, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + return false; + } + + const json* value = nullptr; + if (parsed.contains("android") && parsed["android"].contains("disableSourceMapURLRewrite")) { + value = &parsed["android"]["disableSourceMapURLRewrite"]; + } else if (parsed.contains("disableSourceMapURLRewrite")) { + value = &parsed["disableSourceMapURLRewrite"]; + } + return value != nullptr && value->is_boolean() && value->get(); + }(); + return !disabled; +} + +// Rewrites the sourceMapURL of outgoing Debugger.scriptParsed / +// Debugger.scriptFailedToParse events from a file url (or a url relative to +// the script's file url) to an absolute nsruntime:// url, so DevTools +// requests the map through the target instead of the frontend host. +std::string MaybeRewriteSourceMapURL(const std::string& message) { + if (message.find("\"Debugger.scriptParsed\"") == std::string::npos && + message.find("\"Debugger.scriptFailedToParse\"") == std::string::npos) { + return message; } - v8::Local message = ( - stringView.is8Bit() ? - v8::String::NewFromOneByte(isolate, reinterpret_cast(stringView.characters8()), v8::NewStringType::kNormal, length) : - v8::String::NewFromTwoByte(isolate, reinterpret_cast(stringView.characters16()), v8::NewStringType::kNormal, length) - ) .ToLocalChecked(); - v8::String::Utf8Value result(isolate, message); - return *result; + + if (!ShouldRewriteSourceMapURLs()) { + return message; + } + + auto parsed = json::parse(message, nullptr, false); + if (parsed.is_discarded() || !parsed.contains("params") || !parsed["params"].is_object()) { + return message; + } + + auto& params = parsed["params"]; + std::string sourceMapURL = params.value("sourceMapURL", ""); + if (sourceMapURL.empty() || sourceMapURL.rfind("data:", 0) == 0 || + sourceMapURL.rfind("http:", 0) == 0 || sourceMapURL.rfind("https:", 0) == 0 || + sourceMapURL.rfind(kSourceMapScheme, 0) == 0) { + return message; + } + + std::string path; + if (sourceMapURL.rfind("file://", 0) == 0) { + path = sourceMapURL.substr(strlen("file://")); + } else if (sourceMapURL[0] == '/') { + path = sourceMapURL; + } else { + // Relative to the script url, e.g. "bundle.js.map". + std::string scriptUrl = params.value("url", ""); + if (scriptUrl.rfind("file://", 0) != 0) { + return message; + } + std::string scriptPath = scriptUrl.substr(strlen("file://")); + auto lastSlash = scriptPath.rfind('/'); + if (lastSlash == std::string::npos) { + return message; + } + path = scriptPath.substr(0, lastSlash + 1) + sourceMapURL; + } + + if (path.empty()) { + return message; + } + + params["sourceMapURL"] = kSourceMapScheme + path; + return JsonDump(parsed); } +} // namespace + JsV8InspectorClient::JsV8InspectorClient(v8::Isolate* isolate) : isolate_(isolate), inspector_(nullptr), @@ -69,6 +217,7 @@ JsV8InspectorClient::JsV8InspectorClient(v8::Isolate* isolate) void JsV8InspectorClient::connect(jobject connection) { JEnv env; + std::lock_guard lock(connectionMutex_); connection_ = env.NewGlobalRef(connection); isConnected_ = true; } @@ -88,8 +237,36 @@ void JsV8InspectorClient::createInspectorSession() { } void JsV8InspectorClient::disconnect() { - if (connection_ == nullptr) { - return; + { + // Streams only have meaning to the frontend that opened them; drop any + // the old connection never read to the end (or never IO.close-d). + std::lock_guard lock(resourceStreamsMutex_); + resourceStreams_.clear(); + } + + // Reset worker sessions first and without the main-isolate Locker: if the + // main isolate is paused, its nested loop owns the Locker and this thread + // blocks below — workers must still get a clean slate (resume if paused, + // fresh session) so the reconnecting frontend can re-attach to them. + { + std::lock_guard lock(workerTargetsMutex_); + autoAttach_ = false; + for (auto& entry : workerTargets_) { + entry.second.announced = false; + entry.second.client->PushMessage(WorkerInspectorClient::kResetSessionMessage); + } + } + + { + std::lock_guard lock(connectionMutex_); + if (connection_ == nullptr) { + return; + } + + JEnv env; + env.DeleteGlobalRef(connection_); + connection_ = nullptr; + isConnected_ = false; } v8::Locker locker(isolate_); @@ -99,11 +276,6 @@ void JsV8InspectorClient::disconnect() { session_->resume(); session_.reset(); - JEnv env; - env.DeleteGlobalRef(connection_); - connection_ = nullptr; - isConnected_ = false; - createInspectorSession(); } @@ -202,6 +374,7 @@ void JsV8InspectorClient::runMessageLoopOnPause(int context_group_id) { terminated_ = false; running_nested_loop_ = true; + isPausedNestedLoop_.store(true, std::memory_order_release); while (!terminated_) { JniLocalRef msg(env.CallStaticObjectMethod(inspectorClass_, getInspectorMessageMethod_, connection_)); if (!msg.IsNull()) { @@ -212,6 +385,7 @@ void JsV8InspectorClient::runMessageLoopOnPause(int context_group_id) { while (v8::platform::PumpMessageLoop(Runtime::platform, isolate_)) { } } + isPausedNestedLoop_.store(false, std::memory_order_release); terminated_ = false; running_nested_loop_ = false; } @@ -233,21 +407,399 @@ void JsV8InspectorClient::doDispatchMessage(const std::string& message) { session_->dispatchProtocolMessage(message_view); } +// Chrome DevTools fetches source maps through the target: it sends +// Network.loadNetworkResource for the resolved sourceMappingURL and reads the +// returned stream with IO.read/IO.close. Neither domain is implemented by +// V8's inspector, so they are served here, on the websocket read thread -- +// the main thread may be blocked in the pause message loop or busy running +// JS. The Target domain and worker-session routing live here for the same +// reason: messages received while the main isolate is paused are dispatched +// by doDispatchMessage straight into the V8 session, so dispatchMessage's +// method handling never runs, and a worker must stay debuggable while the +// main isolate is paused (and vice versa). None of this touches V8 except +// the thread-safe RequestInterrupt. +bool JsV8InspectorClient::handleMessageOnSocketThread(const std::string& message, std::string& response) { + auto parsed = json::parse(message, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + return false; + } + + std::string method = parsed.contains("method") && parsed["method"].is_string() + ? parsed["method"].get() + : ""; + long long msgId = parsed.contains("id") && parsed["id"].is_number() + ? parsed["id"].get() + : -1; + std::string sessionId = parsed.contains("sessionId") && parsed["sessionId"].is_string() + ? parsed["sessionId"].get() + : ""; + + // Network/IO first: they serve any session (sessionId echoed), including + // worker source maps. + if (method == "Network.loadNetworkResource") { + std::string url; + if (parsed.contains("params") && parsed["params"].contains("url") && parsed["params"]["url"].is_string()) { + url = parsed["params"]["url"].get(); + } + response = HandleLoadNetworkResource(msgId, url, sessionId); + return true; + } + + if (method == "IO.read" || method == "IO.close") { + std::string handle; + int size = 0; + if (parsed.contains("params")) { + const auto& params = parsed["params"]; + if (params.contains("handle") && params["handle"].is_string()) { + handle = params["handle"].get(); + } + if (params.contains("size") && params["size"].is_number_integer()) { + size = params["size"].get(); + } + } + response = method == "IO.read" + ? HandleIORead(msgId, handle, size, sessionId) + : HandleIOClose(msgId, handle, sessionId); + return true; + } + + // DevTools discovers worker targets through the Target domain: its + // ChildTargetManager sends Target.setAutoAttach {flatten: true} right + // after connecting, and from then on expects Target.attachedToTarget / + // Target.detachedFromTarget events. + if (method == "Target.setAutoAttach") { + bool autoAttach = parsed.contains("params") && parsed["params"].contains("autoAttach") && + parsed["params"]["autoAttach"].is_boolean() && + parsed["params"]["autoAttach"].get(); + if (sessionId.empty()) { + { + std::lock_guard lock(workerTargetsMutex_); + autoAttach_ = autoAttach; + } + // The ack must reach the frontend before any attachedToTarget + // events, so it is sent here instead of through `response` (which + // the socket only writes after this method returns). + json ack = {{"id", msgId}, {"result", json::object()}}; + this->SendToFrontend(JsonDump(ack)); + if (autoAttach) { + this->AnnounceWorkerTargets(); + } + } else { + // Workers have no child targets of their own; just ack. + json ack = {{"id", msgId}, {"result", json::object()}}; + this->SendToFrontend(FinishResponse(ack, sessionId)); + } + return true; + } + + if (method == "Target.setDiscoverTargets" || method == "Target.setRemoteLocations" || + method == "Target.detachFromTarget") { + json ack = {{"id", msgId}, {"result", json::object()}}; + response = FinishResponse(ack, sessionId); + return true; + } + + // Flat-session protocol: a top-level sessionId addresses a worker target. + if (!sessionId.empty()) { + this->RouteToWorker(sessionId, method, msgId, message); + return true; + } + + // Debugger.pause needs to interrupt V8 even if the main thread is busy + // executing JS; RequestInterrupt fires at the next safe bytecode boundary. + // The message still flows through the dispatch queue so V8 answers the + // request id. Skip it while paused in the nested message loop: no JS is + // running, so the interrupt would only fire after the user resumes, + // causing a spurious re-pause. + if (method == "Debugger.pause" && !isPausedNestedLoop_.load(std::memory_order_acquire)) { + isolate_->RequestInterrupt( + [](v8::Isolate* isolate, void* data) { + auto* client = static_cast(data); + if (client->session_ != nullptr) { + client->session_->schedulePauseOnNextStatement({}, {}); + } + }, + this); + } + + return false; +} + +void JsV8InspectorClient::RouteToWorker(const std::string& sessionId, const std::string& method, + long long msgId, const std::string& message) { + // Built under the lock, sent after releasing it: a blocking socket write + // must not stall worker register/unregister on other threads. + std::string errorPayload; + { + std::lock_guard lock(workerTargetsMutex_); + auto it = workerTargets_.find(sessionId); + if (it == workerTargets_.end()) { + // The worker died (or never existed): answer commands so the + // frontend does not wait forever. + if (msgId >= 0) { + json error = {{"id", msgId}, + {"sessionId", sessionId}, + {"error", {{"code", -32001}, {"message", "Session not found"}}}}; + errorPayload = JsonDump(error); + } + } else { + // The lock keeps the client alive against a concurrent + // UnregisterWorkerTarget + delete on the worker thread; both calls + // below are non-blocking (queue push / RequestInterrupt), so they + // never hold the lock across a socket write. + WorkerInspectorClient* client = it->second.client; + + // Debugger.pause must interrupt a busy worker; skip it while the + // worker sits in its nested pause loop (no JS running - the + // interrupt would only fire after resume, causing a spurious + // re-pause). The message is still pushed so V8 answers the id. + if (method == "Debugger.pause" && !client->IsRunningPauseLoop()) { + client->RequestPauseInterrupt(); + } + + client->PushMessage(message); + } + } + + if (!errorPayload.empty()) { + this->SendToFrontend(errorPayload); + } +} + +void JsV8InspectorClient::RegisterWorkerTarget(int workerId, WorkerInspectorClient* client) { + std::string attachedPayload; + { + std::lock_guard lock(workerTargetsMutex_); + WorkerTarget target{workerId, client, false}; + + if (isConnected_ && autoAttach_) { + target.announced = true; + attachedPayload = BuildWorkerAttachedEvent(client); + } + + workerTargets_.emplace(client->SessionId(), target); + } + + if (!attachedPayload.empty()) { + this->SendToFrontend(attachedPayload); + } +} + +void JsV8InspectorClient::UnregisterWorkerTarget(int workerId) { + std::string detachedPayload; + { + std::lock_guard lock(workerTargetsMutex_); + for (auto it = workerTargets_.begin(); it != workerTargets_.end(); ++it) { + if (it->second.workerId != workerId) { + continue; + } + + if (it->second.announced && isConnected_) { + json detached = {{"method", "Target.detachedFromTarget"}, + {"params", + {{"sessionId", it->second.client->SessionId()}, + {"targetId", it->second.client->TargetId()}}}}; + detachedPayload = JsonDump(detached); + } + + workerTargets_.erase(it); + break; + } + } + + if (!detachedPayload.empty()) { + this->SendToFrontend(detachedPayload); + } +} + +void JsV8InspectorClient::AnnounceWorkerTargets() { + std::vector pending; + { + std::lock_guard lock(workerTargetsMutex_); + for (auto& entry : workerTargets_) { + WorkerTarget& target = entry.second; + if (target.announced) { + continue; + } + target.announced = true; + pending.push_back(BuildWorkerAttachedEvent(target.client)); + } + } + + for (const auto& payload : pending) { + this->SendToFrontend(payload); + } +} + +void JsV8InspectorClient::SchedulePauseInWorker(int workerId) { + // Runs on the worker's own thread from a V8 interrupt; re-resolved through + // the registry so a late interrupt after the worker died is a no-op. + std::lock_guard lock(workerTargetsMutex_); + for (auto& entry : workerTargets_) { + if (entry.second.workerId == workerId) { + entry.second.client->SchedulePauseFromInterrupt(); + return; + } + } +} + +std::string JsV8InspectorClient::HandleLoadNetworkResource(long long msgId, const std::string& url, const std::string& sessionId) { + std::string path; + if (url.rfind(kSourceMapScheme, 0) == 0) { + path = url.substr(strlen(kSourceMapScheme)); + } else if (url.rfind("file://", 0) == 0) { + path = url.substr(strlen("file://")); + } else { + // Reply with a protocol error (not success:false) so DevTools falls + // back to loading the resource from the frontend host, which is the + // pre-existing behavior for http(s) urls. + json error = { + {"id", msgId}, + {"error", {{"code", -32000}, {"message", "Unsupported URL scheme"}}} + }; + return FinishResponse(error, sessionId); + } + + std::string content; + bool loaded = false; + + if (!path.empty()) { + std::vector candidates; + auto addCandidate = [&candidates](const std::string& candidate) { + if (!candidate.empty() && + std::find(candidates.begin(), candidates.end(), candidate) == candidates.end()) { + candidates.push_back(candidate); + } + }; + auto addVariants = [&addCandidate](const std::string& p) { + addCandidate(p); + if (p[0] != '/') { + addCandidate(Constants::APP_ROOT_FOLDER_PATH + p); + } + // /data/data/ and /data/user/0/ alias the same files; + // urls may use either spelling depending on who built them. + constexpr const char* kDataData = "/data/data/"; + constexpr const char* kDataUser0 = "/data/user/0/"; + if (p.rfind(kDataData, 0) == 0) { + addCandidate(kDataUser0 + p.substr(strlen(kDataData))); + } else if (p.rfind(kDataUser0, 0) == 0) { + addCandidate(kDataData + p.substr(strlen(kDataUser0))); + } + }; + + addVariants(path); + // fall back to percent-decoded variants for urls the frontend encoded + std::string decoded = ada::unicode::percent_decode(path, path.find('%')); + if (decoded != path) { + addVariants(decoded); + } + + for (const auto& candidate : candidates) { + int length = 0; + auto* data = static_cast(File::ReadBinary(candidate, length)); + if (data != nullptr) { + content.assign(reinterpret_cast(data), length); + delete[] data; + loaded = true; + break; + } + } + } + + json resource; + if (loaded) { + std::string handle; + { + std::lock_guard lock(resourceStreamsMutex_); + handle = "ns-network-resource-" + std::to_string(++lastStreamId_); + resourceStreams_[handle] = {std::move(content), 0}; + } + resource = {{"success", true}, {"httpStatusCode", 200}, {"stream", handle}}; + } else { + resource = {{"success", false}, + {"netError", -6}, + {"netErrorName", "net::ERR_FILE_NOT_FOUND"}, + {"httpStatusCode", 404}}; + } + + json response = {{"id", msgId}, {"result", {{"resource", resource}}}}; + return FinishResponse(response, sessionId); +} + +std::string JsV8InspectorClient::HandleIORead(long long msgId, const std::string& handle, int size, const std::string& sessionId) { + std::lock_guard lock(resourceStreamsMutex_); + auto it = resourceStreams_.find(handle); + if (it == resourceStreams_.end()) { + json error = { + {"id", msgId}, + {"error", {{"code", -32602}, {"message", "Invalid stream handle"}}} + }; + return FinishResponse(error, sessionId); + } + + ResourceStream& stream = it->second; + constexpr size_t kDefaultChunkSize = 1024 * 1024; + size_t chunkSize = size > 0 ? static_cast(size) : kDefaultChunkSize; + size_t remaining = stream.data.size() - stream.offset; + chunkSize = std::min(chunkSize, remaining); + + json result; + if (chunkSize == 0) { + // DevTools ignores any data sent alongside eof, so only signal it once + // the whole stream has been delivered. + result = {{"data", ""}, {"eof", true}, {"base64Encoded", false}}; + } else { + // Base64 keeps arbitrary file bytes intact through the JSON transport. + std::string encoded = Base64Encode(stream.data.data() + stream.offset, chunkSize); + stream.offset += chunkSize; + result = {{"data", encoded}, {"eof", false}, {"base64Encoded", true}}; + } + + json response = {{"id", msgId}, {"result", result}}; + return FinishResponse(response, sessionId); +} + +std::string JsV8InspectorClient::HandleIOClose(long long msgId, const std::string& handle, const std::string& sessionId) { + { + std::lock_guard lock(resourceStreamsMutex_); + resourceStreams_.erase(handle); + } + json response = {{"id", msgId}, {"result", json::object()}}; + return FinishResponse(response, sessionId); +} + void JsV8InspectorClient::sendResponse(int callId, std::unique_ptr message) { sendNotification(std::move(message)); } void JsV8InspectorClient::sendNotification(std::unique_ptr message) { - if (connection_ == nullptr) { - return; - } - - const std::string msg = stringViewToString(isolate_, message->string()); + this->SendToFrontend(v8_inspector::toString16(message->string()).utf8()); +} +void JsV8InspectorClient::SendToFrontend(const std::string& message) { JEnv env; - // TODO: Pete: Check if we can use a wide (utf 16) string here - JniLocalRef str(env.NewStringUTF(msg.c_str())); - env.CallStaticVoidMethod(inspectorClass_, sendMethod_, connection_, (jstring) str); + JniLocalRef connection; + { + std::lock_guard lock(connectionMutex_); + if (connection_ == nullptr) { + return; + } + // A local ref keeps the websocket reachable after disconnect() drops + // the global ref; the (synchronized, possibly blocking) socket write + // must not run under the mutex or it would block disconnect(). + connection = JniLocalRef(env.NewLocalRef(connection_)); + } + + const std::string msg = MaybeRewriteSourceMapURL(message); + try { + // TODO: Pete: Check if we can use a wide (utf 16) string here + JniLocalRef str(env.NewStringUTF(msg.c_str())); + env.CallStaticVoidMethod(inspectorClass_, sendMethod_, (jobject) connection, (jstring) str); + } catch (NativeScriptException& e) { + // The socket died mid-send; the frontend is gone anyway. This must not + // unwind into V8 inspector internals or a worker's pause loop. + env.ExceptionClear(); + } } void JsV8InspectorClient::flushProtocolNotifications() { @@ -265,7 +817,11 @@ void JsV8InspectorClient::init() { inspector_ = V8Inspector::create(isolate_, this); - inspector_->contextCreated(v8_inspector::V8ContextInfo(context, JsV8InspectorClient::contextGroupId, {})); + // Name the context: the DevTools console context selector labels entries + // with the context's name; workers are labeled with their script url. + static const std::string mainContextName = "main"; + inspector_->contextCreated(v8_inspector::V8ContextInfo( + context, JsV8InspectorClient::contextGroupId, stringToStringView(mainContextName))); context_.Reset(isolate_, context); @@ -282,34 +838,48 @@ void JsV8InspectorClient::init() { } JsV8InspectorClient* JsV8InspectorClient::GetInstance() { - if (instance == nullptr) { - instance = new JsV8InspectorClient(Runtime::GetRuntime(0)->GetIsolate()); + JsV8InspectorClient* client = instance.load(std::memory_order_acquire); + if (client != nullptr) { + return client; + } + + // handleMessageOnSocketThread also calls this from the socket thread, so a + // concurrent first call is possible: construct, then publish with a CAS and + // discard our copy if another thread won the race. + auto* created = new JsV8InspectorClient(Runtime::GetRuntime(0)->GetIsolate()); + JsV8InspectorClient* expected = nullptr; + if (!instance.compare_exchange_strong(expected, created, std::memory_order_acq_rel, + std::memory_order_acquire)) { + delete created; + return expected; } - return instance; + return created; +} + +JsV8InspectorClient* JsV8InspectorClient::GetInstanceIfCreated() { + return instance.load(std::memory_order_acquire); } void JsV8InspectorClient::inspectorSendEventCallback(const FunctionCallbackInfo& args) { - if ((instance == nullptr) || (instance->connection_ == nullptr)) { + JsV8InspectorClient* client = GetInstanceIfCreated(); + if (client == nullptr || !client->isConnected_) { return; } - Isolate* isolate = args.GetIsolate(); Local arg = args[0].As(); std::string message = ArgConverter::ConvertToString(arg); - JEnv env; - // TODO: Pete: Check if we can use a wide (utf 16) string here - JniLocalRef str(env.NewStringUTF(message.c_str())); - env.CallStaticVoidMethod(instance->inspectorClass_, instance->sendMethod_, instance->connection_, (jstring) str); + client->SendToFrontend(message); // TODO: ios uses this method, but doesn't work on android // so I'm just sending directly to the socket (which seems to work) - instance->dispatchMessage(message); + client->dispatchMessage(message); } void JsV8InspectorClient::sendToFrontEndCallback(const v8::FunctionCallbackInfo& args) { - if ((instance == nullptr) || (instance->connection_ == nullptr)) { + JsV8InspectorClient* client = GetInstanceIfCreated(); + if (client == nullptr) { return; } @@ -325,9 +895,18 @@ void JsV8InspectorClient::sendToFrontEndCallback(const v8::FunctionCallbackInfo< } JEnv env; + JniLocalRef connection; + { + std::lock_guard lock(client->connectionMutex_); + if (client->connection_ == nullptr) { + return; + } + connection = JniLocalRef(env.NewLocalRef(client->connection_)); + } + JniLocalRef str(env.NewStringUTF(message.c_str())); JniLocalRef lev(env.NewStringUTF(level.c_str())); - env.CallStaticVoidMethod(instance->inspectorClass_, instance->sendToDevToolsConsoleMethod_, instance->connection_, (jstring) str, (jstring)lev); + env.CallStaticVoidMethod(client->inspectorClass_, client->sendToDevToolsConsoleMethod_, (jobject) connection, (jstring) str, (jstring)lev); } } catch (NativeScriptException& e) { e.ReThrowToV8(); @@ -343,25 +922,38 @@ void JsV8InspectorClient::sendToFrontEndCallback(const v8::FunctionCallbackInfo< } void JsV8InspectorClient::consoleLogCallback(Isolate* isolate, ConsoleAPIType method, const std::vector>& args) { - if (!inspectorIsConnected()) { + // Worker isolates route to their own inspector (we're on the worker's + // thread here). Checked first so worker logging never lazily constructs + // the root client. + WorkerWrapper* worker = WorkerWrapper::FromIsolate(isolate); + if (worker != nullptr) { + worker->ConsoleLog(method, args); + return; + } + + JsV8InspectorClient* client = GetInstanceIfCreated(); + if (client == nullptr || client->inspector_ == nullptr) { return; } // Note, here we access private V8 API - auto* impl = reinterpret_cast(instance->inspector_.get()); - auto* session = reinterpret_cast(instance->session_.get()); + auto* impl = reinterpret_cast(client->inspector_.get()); std::unique_ptr stack = impl->debugger()->captureStackTrace(false); - v8::Local context = instance->context_.Get(instance->isolate_); + v8::Local context = client->context_.Get(client->isolate_); const int contextId = V8ContextInfo::executionContextId(context); std::unique_ptr msg = v8_inspector::V8ConsoleMessage::createForConsoleAPI( - context, contextId, contextGroupId, impl, instance->currentTimeMS(), + context, contextId, contextGroupId, impl, client->currentTimeMS(), method, args, String16{}, std::move(stack)); - session->runtimeAgent()->messageAdded(msg.get()); + // Going through the message storage both reports to the session when the + // frontend has enabled the Runtime agent AND keeps the message for replay + // on Runtime.enable, so anything logged before the frontend attaches + // shows up as console history. + impl->ensureConsoleMessageStorage(contextGroupId)->addMessage(std::move(msg)); } void JsV8InspectorClient::registerDomainDispatcherCallback(const FunctionCallbackInfo& args) { @@ -440,7 +1032,7 @@ void JsV8InspectorClient::InspectorIsConnectedGetterCallback(v8::LocalisConnected_); } -JsV8InspectorClient* JsV8InspectorClient::instance = nullptr; +std::atomic JsV8InspectorClient::instance{nullptr}; bool JsV8InspectorClient::CallDomainHandlerFunction(Local context, Local domainMethodFunc, const Local& arg, Local& domainDebugger, Local& result) { diff --git a/test-app/runtime/src/main/cpp/JsV8InspectorClient.h b/test-app/runtime/src/main/cpp/JsV8InspectorClient.h index 2b26e5e56..b28257ba3 100644 --- a/test-app/runtime/src/main/cpp/JsV8InspectorClient.h +++ b/test-app/runtime/src/main/cpp/JsV8InspectorClient.h @@ -1,6 +1,9 @@ #ifndef JSV8INSPECTORCLIENT_H_ #define JSV8INSPECTORCLIENT_H_ +#include +#include +#include #include #include #include @@ -12,16 +15,39 @@ using namespace v8_inspector; namespace tns { +class WorkerInspectorClient; + class JsV8InspectorClient : V8InspectorClient, v8_inspector::V8Inspector::Channel { public: static JsV8InspectorClient* GetInstance(); + // Non-constructing variant for worker threads: returns nullptr until a + // main-thread entry point has created the client. + static JsV8InspectorClient* GetInstanceIfCreated(); + void init(); void connect(jobject connection); void scheduleBreak(); void disconnect(); void dispatchMessage(const std::string& message); + // Any thread. Rewrites source map urls and writes to the frontend + // socket; serializes against connect/disconnect. + void SendToFrontend(const std::string& message); + + // Worker target management (Target domain, flat-session protocol). + // Register/Unregister run on the worker's own thread; SchedulePauseInWorker + // runs on the worker thread from a V8 interrupt. + void RegisterWorkerTarget(int workerId, WorkerInspectorClient* client); + void UnregisterWorkerTarget(int workerId); + void SchedulePauseInWorker(int workerId); + + // Runs on the websocket read thread. Returns true when the message was + // handled there (response, possibly empty when replies were already + // sent or none is needed, goes back to the socket); false routes the + // message through the normal main-thread dispatch queue. + bool handleMessageOnSocketThread(const std::string& message, std::string& response); + void registerModules(); // Overrides of V8Inspector::Channel @@ -56,9 +82,49 @@ class JsV8InspectorClient : V8InspectorClient, v8_inspector::V8Inspector::Channe static void InspectorIsConnectedGetterCallback(v8::Local property, const v8::PropertyCallbackInfo& info); - static JsV8InspectorClient* instance; + static std::atomic instance; static constexpr int contextGroupId = 1; + // Streams backing Network.loadNetworkResource responses, read by the + // frontend through IO.read/IO.close (how Chrome DevTools fetches source + // maps from the target). Accessed from the websocket read thread. + struct ResourceStream { + std::string data; + size_t offset = 0; + }; + + // Live worker inspectors, keyed by their flat-protocol sessionId + // ("NS_WORKER_"). Entries are added/removed from worker threads and + // read from the socket thread. + struct WorkerTarget { + int workerId; + WorkerInspectorClient* client; + bool announced = false; + }; + + void RouteToWorker(const std::string& sessionId, const std::string& method, + long long msgId, const std::string& message); + void AnnounceWorkerTargets(); + + std::map workerTargets_; + std::mutex workerTargetsMutex_; + bool autoAttach_ = false; // guarded by workerTargetsMutex_ + + // Lock order: workerTargetsMutex_ -> connectionMutex_ (registry walks + // send while holding the registry lock); never the other way around. + std::mutex connectionMutex_; + + // Source map delivery to Chrome DevTools (Network.loadNetworkResource + + // IO domain). V8's inspector doesn't implement these embedder domains. + std::string HandleLoadNetworkResource(long long msgId, const std::string& url, const std::string& sessionId); + std::string HandleIORead(long long msgId, const std::string& handle, int size, const std::string& sessionId); + std::string HandleIOClose(long long msgId, const std::string& handle, const std::string& sessionId); + + std::map resourceStreams_; + std::mutex resourceStreamsMutex_; + int lastStreamId_ = 0; + std::atomic isPausedNestedLoop_{false}; + std::unique_ptr tracing_agent_; v8::Isolate* isolate_; std::unique_ptr inspector_; @@ -71,7 +137,10 @@ class JsV8InspectorClient : V8InspectorClient, v8_inspector::V8Inspector::Channe jobject connection_; bool running_nested_loop_ : 1; bool terminated_ : 1; - bool isConnected_ : 1; + // Read from worker threads (target announcements); must not share a + // memory location with the bitfields above, which the main thread + // writes. + std::atomic isConnected_; // {N} specific helpers diff --git a/test-app/runtime/src/main/cpp/LooperTasks.cpp b/test-app/runtime/src/main/cpp/LooperTasks.cpp new file mode 100644 index 000000000..7b4f353ec --- /dev/null +++ b/test-app/runtime/src/main/cpp/LooperTasks.cpp @@ -0,0 +1,105 @@ +#include "LooperTasks.h" + +#include +#include + +#include +#include +#include + +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" + +namespace tns { + +void LooperTasks::Initialize(ALooper* looper) { + std::lock_guard lock(mutex_); + + int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd == -1) { + DEBUG_WRITE_FORCE("LooperTasks: eventfd failed: %s", strerror(errno)); + return; + } + + if (ALooper_addFd(looper, fd, ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, + LooperTasks::TasksReadyCallback, this) != 1) { + DEBUG_WRITE_FORCE("LooperTasks: ALooper_addFd failed"); + close(fd); + return; + } + + looper_ = looper; + ALooper_acquire(looper_); + fd_ = fd; +} + +void LooperTasks::Post(std::function task) { + std::lock_guard lock(mutex_); + if (terminated_) { + // The owning runtime is shutting down (or gone) - drop the task, + // matching the old "message to a terminated worker/main" semantics. + return; + } + + tasks_.push(std::move(task)); + + if (fd_ != -1) { + uint64_t value = 1; + write(fd_, &value, sizeof(value)); + } +} + +void LooperTasks::Terminate() { + // Must run on the looper's own thread: removing an fd concurrently with an + // in-flight callback dispatch is racy. + std::lock_guard lock(mutex_); + terminated_ = true; + + if (fd_ != -1) { + ALooper_removeFd(looper_, fd_); + close(fd_); + fd_ = -1; + } + + if (looper_ != nullptr) { + ALooper_release(looper_); + looper_ = nullptr; + } +} + +int LooperTasks::TasksReadyCallback(int fd, int events, void* data) { + uint64_t value; + read(fd, &value, sizeof(value)); + + static_cast(data)->Drain(); + return 1; +} + +void LooperTasks::Drain() { + std::vector> tasks; + { + std::lock_guard lock(mutex_); + while (!tasks_.empty()) { + tasks.push_back(std::move(tasks_.front())); + tasks_.pop(); + } + } + + for (auto& task : tasks) { + // A C++ exception must never propagate out of an ALooper callback. + // Tasks that need to surface JS/Java errors do so themselves + // (e.g. via NativeScriptException::ReThrowToJava, which only sets a + // pending Java exception). + try { + task(); + } catch (NativeScriptException& ex) { + ex.ReThrowToJava(); + } catch (std::exception& ex) { + DEBUG_WRITE_FORCE("Error: c++ exception in looper task: %s", ex.what()); + } catch (...) { + DEBUG_WRITE_FORCE("Error: unknown c++ exception in looper task!"); + } + } +} + +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/LooperTasks.h b/test-app/runtime/src/main/cpp/LooperTasks.h new file mode 100644 index 000000000..ba5409e6b --- /dev/null +++ b/test-app/runtime/src/main/cpp/LooperTasks.h @@ -0,0 +1,41 @@ +#ifndef LOOPERTASKS_H_ +#define LOOPERTASKS_H_ + +#include +#include +#include +#include + +namespace tns { + +/* + * A task queue bound to one runtime's looper - the Android analogue of the + * iOS runtime's ExecuteOnRunLoop(runtime->RuntimeLoop(), ...). + * Each Runtime (main or worker) owns one; child workers post their outbound + * messages, errors and cleanup notifications to their parent runtime's queue. + * + * Post() may be called from any thread; tasks posted after Terminate() are + * dropped. Initialize()/Terminate() must run on the looper's own thread. + * Held via shared_ptr by the owning Runtime and via weak_ptr by child + * WorkerWrappers, so a child posting to an already-destroyed parent is safe. + */ +class LooperTasks { +public: + void Initialize(ALooper* looper); + void Post(std::function task); + void Terminate(); + +private: + static int TasksReadyCallback(int fd, int events, void* data); + void Drain(); + + std::mutex mutex_; + std::queue> tasks_; + ALooper* looper_ = nullptr; + int fd_ = -1; + bool terminated_ = false; +}; + +} // namespace tns + +#endif /* LOOPERTASKS_H_ */ diff --git a/test-app/runtime/src/main/cpp/MethodCache.cpp b/test-app/runtime/src/main/cpp/MethodCache.cpp index 587933728..24a51afac 100644 --- a/test-app/runtime/src/main/cpp/MethodCache.cpp +++ b/test-app/runtime/src/main/cpp/MethodCache.cpp @@ -135,7 +135,7 @@ string MethodCache::GetType(Isolate* isolate, const v8::Local& value) if (value->IsArray()) { type = "array"; - } else if (value->IsArrayBuffer() || value->IsInt8Array() || value->IsUint8Array() || value->IsUint8ClampedArray()) { + } else if (value->IsArrayBuffer() || value->IsSharedArrayBuffer() || value->IsInt8Array() || value->IsUint8Array() || value->IsUint8ClampedArray()) { type = "bytebuffer"; } else if (value->IsInt16Array() || value->IsUint16Array()) { type = "shortbuffer"; diff --git a/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp b/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp index 6beb7124f..73d04a557 100644 --- a/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp +++ b/test-app/runtime/src/main/cpp/ModuleInternalCallbacks.cpp @@ -98,6 +98,32 @@ static void LogHttpCompileDiagnostics(v8::Isolate* isolate, snippet.c_str()); } +// Helper: collapse "." and ".." path segments, preserving a leading "/". +static std::string NormalizeDotSegments(const std::string& path) { + std::vector stack; + bool absolute = !path.empty() && path[0] == '/'; + size_t i = 0; + while (i <= path.size()) { + size_t j = path.find('/', i); + std::string seg = (j == std::string::npos) ? path.substr(i) : path.substr(i, j - i); + if (seg.empty() || seg == ".") { + // skip + } else if (seg == "..") { + if (!stack.empty()) stack.pop_back(); + } else { + stack.push_back(seg); + } + if (j == std::string::npos) break; + i = j + 1; + } + std::string norm = absolute ? "/" : std::string(); + for (size_t k = 0; k < stack.size(); k++) { + if (k > 0) norm += "/"; + norm += stack[k]; + } + return norm; +} + // Helper: resolve relative or root-absolute spec against an HTTP(S) referrer URL. // Returns empty string if resolution is not possible. static std::string ResolveHttpRelative(const std::string& referrerUrl, const std::string& spec) { @@ -158,28 +184,30 @@ static std::string ResolveHttpRelative(const std::string& referrerUrl, const std } // Normalize "." and ".." segments - std::vector stack; - bool absolute = !newPath.empty() && newPath[0] == '/'; - size_t i = 0; - while (i <= newPath.size()) { - size_t j = newPath.find('/', i); - std::string seg = (j == std::string::npos) ? newPath.substr(i) : newPath.substr(i, j - i); - if (seg.empty() || seg == ".") { - // skip - } else if (seg == "..") { - if (!stack.empty()) stack.pop_back(); - } else { - stack.push_back(seg); - } - if (j == std::string::npos) break; - i = j + 1; + std::string normPath = NormalizeDotSegments(newPath); + return origin + normPath + specSuffix; +} + +// Helper: resolve a relative "./" or "../" specifier against a file:// referrer +// URL, returning an absolute file:// URL. Returns empty if not applicable. +static std::string ResolveFileRelative(const std::string& referrerUrl, const std::string& spec) { + const std::string filePrefix = "file://"; + if (referrerUrl.rfind(filePrefix, 0) != 0) { + return std::string(); } - std::string normPath = absolute ? "/" : std::string(); - for (size_t k = 0; k < stack.size(); k++) { - if (k > 0) normPath += "/"; - normPath += stack[k]; + if (spec.empty() || spec[0] != '.') { + return std::string(); } - return origin + normPath + specSuffix; + // Referrer path: strip scheme, drop query and fragment + std::string refPath = referrerUrl.substr(filePrefix.size()); + size_t hashPos = refPath.find('#'); + if (hashPos != std::string::npos) refPath = refPath.substr(0, hashPos); + size_t qPos = refPath.find('?'); + if (qPos != std::string::npos) refPath = refPath.substr(0, qPos); + + size_t lastSlash = refPath.find_last_of('/'); + std::string baseDir = (lastSlash == std::string::npos) ? std::string("/") : refPath.substr(0, lastSlash + 1); + return filePrefix + NormalizeDotSegments(baseDir + spec); } // Import meta callback to support import.meta.url and import.meta.dirname @@ -565,6 +593,14 @@ v8::MaybeLocal ResolveModuleCallback(v8::Local context, if (found) break; } + // Canonicalize "." / ".." segments so a file reached through different + // spellings (e.g. "./x" from /a/b and "../x" from /a/b/c both name /a/b/x) + // maps to one registry key and is compiled once. The HTTP branch + // canonicalizes via CanonicalizeHttpUrlKey. + if (found) { + absPath = NormalizeDotSegments(absPath); + } + // 6) Handle special cases if file not found if (!found) { // Check for Node.js built-in modules @@ -928,12 +964,28 @@ v8::MaybeLocal ImportModuleDynamicallyCallback( // Re-use the static resolver to locate / compile the module for non-HTTP cases. try { - // Pass empty referrer since this V8 version doesn't expose GetModule() on - // ScriptOrModule. The resolver will fall back to absolute-path heuristics. + // V8 exposes only the referrer's URL here (resource_name), not its Module, + // so anchor a relative specifier at the referrer's directory and hand the + // resolver an absolute file:// URL. Other specifiers pass through unchanged + // (the resolver applies its own ~/, bare and absolute heuristics). + v8::Local resolvedSpecifier = specifier; + if (specIsRelative) { + std::string fileResolved = ResolveFileRelative(referrerUrl, spec); + if (!fileResolved.empty()) { + resolvedSpecifier = ArgConverter::ConvertToV8String(isolate, fileResolved); + if (IsScriptLoadingLogEnabled()) { + DEBUG_WRITE("[esm][dyn][file-rel] base=%s spec=%s -> %s", + referrerUrl.c_str(), spec.c_str(), fileResolved.c_str()); + } + } + } + + // Pass empty referrer: this V8 version does not expose GetModule() on + // ScriptOrModule, and the specifier above is already absolute when needed. v8::Local refMod; v8::MaybeLocal maybeModule = - ResolveModuleCallback(context, specifier, import_assertions, refMod); + ResolveModuleCallback(context, resolvedSpecifier, import_assertions, refMod); v8::Local module; if (!maybeModule.ToLocal(&module)) { diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index aab1050ec..df0fb6e60 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -137,7 +137,10 @@ Runtime::Runtime(JNIEnv* env, jobject runtime, int id) m_runtime = env->NewGlobalRef(runtime); m_objectManager = new ObjectManager(m_runtime); m_loopTimer = new MessageLoopTimer(); - s_id2RuntimeCache.emplace(id, this); + { + std::lock_guard lock(s_runtimeCacheMutex); + s_id2RuntimeCache.emplace(id, this); + } if (GET_USED_MEMORY_METHOD_ID == nullptr) { auto RUNTIME_CLASS = env->FindClass("com/tns/Runtime"); @@ -150,9 +153,12 @@ Runtime::Runtime(JNIEnv* env, jobject runtime, int id) } Runtime* Runtime::GetRuntime(int runtimeId) { - auto itFound = s_id2RuntimeCache.find(runtimeId); - auto runtime = - (itFound != s_id2RuntimeCache.end()) ? itFound->second : nullptr; + Runtime* runtime = nullptr; + { + std::lock_guard lock(s_runtimeCacheMutex); + auto itFound = s_id2RuntimeCache.find(runtimeId); + runtime = (itFound != s_id2RuntimeCache.end()) ? itFound->second : nullptr; + } if (runtime == nullptr) { stringstream ss; @@ -164,15 +170,31 @@ Runtime* Runtime::GetRuntime(int runtimeId) { } Runtime* Runtime::GetRuntime(v8::Isolate* isolate) { - auto it = s_isolate2RuntimesCache.find(isolate); + /* + * Hot path, called from V8 callbacks on the isolate's own thread. The slot + * is written once in PrepareV8Runtime before the isolate runs any JS, so + * reading it requires no lock. + */ + auto runtime = static_cast( + isolate->GetData((uint32_t)Runtime::IsolateData::RUNTIME)); + if (runtime != nullptr) { + return runtime; + } - if (it == s_isolate2RuntimesCache.end()) { + // covers the window during isolate setup before SetData has run + { + std::lock_guard lock(s_runtimeCacheMutex); + auto it = s_isolate2RuntimesCache.find(isolate); + runtime = (it != s_isolate2RuntimesCache.end()) ? it->second : nullptr; + } + + if (runtime == nullptr) { stringstream ss; ss << "Cannot find runtime for isolate: " << isolate; throw NativeScriptException(ss.str()); } - return it->second; + return runtime; } Runtime* Runtime::GetRuntimeFromIsolateData(v8::Isolate* isolate) { @@ -296,9 +318,7 @@ void Runtime::RunModule(const char* moduleName) { m_module.Load(context, moduleName); } -void Runtime::RunWorker(jstring scriptFile) { - // TODO: Pete: Why do I crash here with a JNI error (accessing bad jni) - string filePath = ArgConverter::jstringToString(scriptFile); +void Runtime::RunWorker(const std::string& filePath) { auto context = this->GetContext(); m_module.LoadWorker(context, filePath); } @@ -518,28 +538,6 @@ void Runtime::PassExceptionToJsNative(JNIEnv* env, jobject obj, NativeScriptException::CallJsFuncWithErr(errObj, isDiscarded); } -void Runtime::PassUncaughtExceptionFromWorkerToMainHandler( - Local message, Local stackTrace, - Local filename, int lineno) { - JEnv env; - auto runtimeClass = env.GetObjectClass(m_runtime); - - auto mId = env.GetStaticMethodID( - runtimeClass, "passUncaughtExceptionFromWorkerToMain", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); - - auto jMsg = ArgConverter::ConvertToJavaString(message); - auto jfileName = ArgConverter::ConvertToJavaString(filename); - auto stckTrace = ArgConverter::ConvertToJavaString(stackTrace); - - JniLocalRef jMsgLocal(jMsg); - JniLocalRef jfileNameLocal(jfileName); - JniLocalRef stTrace(stckTrace); - - env.CallStaticVoidMethod(runtimeClass, mId, (jstring)jMsgLocal, - (jstring)jfileNameLocal, (jstring)stTrace, lineno); -} - static void InitializeV8() { Runtime::platform = v8::platform::NewDefaultPlatform().release(); V8::InitializePlatform(Runtime::platform); @@ -576,7 +574,10 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, m_realtimeOrigin = platform->CurrentClockTimeMillis(); isolateFrame.log("Isolate.New"); - s_isolate2RuntimesCache[isolate] = this; + { + std::lock_guard lock(s_runtimeCacheMutex); + s_isolate2RuntimesCache[isolate] = this; + } v8::Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handleScope(isolate); @@ -698,11 +699,6 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "URLPattern"), URLPatternImpl::GetCtor(isolate)); - /* - * Attach `Worker` object constructor only to the main thread (isolate)'s - * global object Workers should not be created from within other Workers, for - * now - */ if (!s_mainThreadInitialized) { m_isMainThread = true; pipe2(m_mainLooper_fd, O_NONBLOCK | O_CLOEXEC); @@ -726,7 +722,32 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, ALooper_addFd(m_mainLooper, m_mainLooper_fd[0], ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, CallbackHandlers::RunOnMainThreadFdCallback, nullptr); + } + /* + * Emulate a `WorkerGlobalScope` + * Attach 'postMessage', 'close' to the global object + */ + else { + m_isMainThread = false; + auto postMessageFuncTemplate = FunctionTemplate::New( + isolate, CallbackHandlers::WorkerGlobalPostMessageCallback); + globalTemplate->Set( + ArgConverter::ConvertToV8String(isolate, "__ns__worker"), + Boolean::New(isolate, true)); + globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "postMessage"), + postMessageFuncTemplate); + auto closeFuncTemplate = FunctionTemplate::New( + isolate, CallbackHandlers::WorkerGlobalCloseCallback); + globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "close"), + closeFuncTemplate); + } + /* + * Attach the `Worker` constructor to every isolate's global object - workers + * may be created from the main thread or from within other workers + * (mirroring the iOS runtime). + */ + { Local workerFuncTemplate = FunctionTemplate::New(isolate, CallbackHandlers::NewThreadCallback); Local prototype = workerFuncTemplate->PrototypeTemplate(); @@ -748,24 +769,15 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "Worker"), workerFuncTemplate); } + /* - * Emulate a `WorkerGlobalScope` - * Attach 'postMessage', 'close' to the global object + * Per-runtime task queue used by child workers to deliver messages, errors + * and cleanup notifications to this runtime's thread. The looper exists for + * every runtime: Java prepares it before initNativeScript on both the main + * and worker threads. */ - else { - m_isMainThread = false; - auto postMessageFuncTemplate = FunctionTemplate::New( - isolate, CallbackHandlers::WorkerGlobalPostMessageCallback); - globalTemplate->Set( - ArgConverter::ConvertToV8String(isolate, "__ns__worker"), - Boolean::New(isolate, true)); - globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "postMessage"), - postMessageFuncTemplate); - auto closeFuncTemplate = FunctionTemplate::New( - isolate, CallbackHandlers::WorkerGlobalCloseCallback); - globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "close"), - closeFuncTemplate); - } + m_looperTasks = std::make_shared(); + m_looperTasks->Initialize(ALooper_forThread()); SimpleProfiler::Init(isolate, globalTemplate); @@ -951,8 +963,16 @@ void Runtime::PerformanceNowCallback( } void Runtime::DestroyRuntime() { - s_id2RuntimeCache.erase(m_id); - s_isolate2RuntimesCache.erase(m_isolate); + { + std::lock_guard lock(s_runtimeCacheMutex); + s_id2RuntimeCache.erase(m_id); + s_isolate2RuntimesCache.erase(m_isolate); + } + if (m_looperTasks != nullptr) { + // runs on this runtime's own thread; children still holding a weak_ptr + // will have their posts dropped from now on + m_looperTasks->Terminate(); + } tns::disposeIsolate(m_isolate); } @@ -970,6 +990,7 @@ JavaVM* Runtime::s_jvm = nullptr; jmethodID Runtime::GET_USED_MEMORY_METHOD_ID = nullptr; robin_hood::unordered_map Runtime::s_id2RuntimeCache; robin_hood::unordered_map Runtime::s_isolate2RuntimesCache; +std::mutex Runtime::s_runtimeCacheMutex; bool Runtime::s_mainThreadInitialized = false; v8::Platform* Runtime::platform = nullptr; int Runtime::m_androidVersion = Runtime::GetAndroidVersion(); diff --git a/test-app/runtime/src/main/cpp/Runtime.h b/test-app/runtime/src/main/cpp/Runtime.h index bedb70e43..c317eebe3 100644 --- a/test-app/runtime/src/main/cpp/Runtime.h +++ b/test-app/runtime/src/main/cpp/Runtime.h @@ -12,6 +12,8 @@ #include "MessageLoopTimer.h" #include "File.h" #include "Timers.h" +#include "LooperTasks.h" +#include #include #include #include @@ -21,7 +23,8 @@ class Runtime { public: enum IsolateData { RUNTIME = 0, - CONSTANTS = 1 + CONSTANTS = 1, + WORKER_WRAPPER = 2 }; ~Runtime(); @@ -51,7 +54,7 @@ class Runtime { void RunModule(JNIEnv* _env, jobject obj, jstring scriptFile); void RunModule(const char *moduleName); - void RunWorker(jstring scriptFile); + void RunWorker(const std::string& filePath); jobject RunScript(JNIEnv* _env, jobject obj, jstring scriptFile); jobject CallJSMethodNative(JNIEnv* _env, jobject obj, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, jobjectArray packagedArgs); void CreateJSInstanceNative(JNIEnv* _env, jobject obj, jobject javaObject, jint javaObjectID, jstring className); @@ -60,7 +63,6 @@ class Runtime { bool NotifyGC(JNIEnv* env, jobject obj); bool TryCallGC(); void PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable exception, jstring message, jstring fullStackTrace, jstring jsStackTrace, jboolean isDiscarded); - void PassUncaughtExceptionFromWorkerToMainHandler(v8::Local message, v8::Local stackTrace, v8::Local filename, int lineno); void DestroyRuntime(); void Lock(); @@ -79,6 +81,18 @@ class Runtime { static ALooper* GetMainLooper() { return m_mainLooper; } + static JavaVM* GetJVM() { + return s_jvm; + } + + /* + * Task queue bound to this runtime's looper. Child workers hold a + * weak_ptr to their parent runtime's queue for worker -> parent + * delivery (messages, errors, cleanup notifications). + */ + std::shared_ptr GetLooperTasks() const { + return m_looperTasks; + } private: Runtime(JNIEnv* env, jobject runtime, int id); @@ -99,6 +113,8 @@ class Runtime { MessageLoopTimer* m_loopTimer; + std::shared_ptr m_looperTasks; + int64_t m_lastUsedMemory; v8::Persistent* m_gcFunc; @@ -126,6 +142,12 @@ class Runtime { static robin_hood::unordered_map s_isolate2RuntimesCache; + /* + * Guards the two caches above: runtimes are now constructed and + * destroyed concurrently on native-spawned worker threads. + */ + static std::mutex s_runtimeCacheMutex; + static JavaVM* s_jvm; static jmethodID GET_USED_MEMORY_METHOD_ID; diff --git a/test-app/runtime/src/main/cpp/Timers.cpp b/test-app/runtime/src/main/cpp/Timers.cpp index 31d417866..ebf9896d5 100644 --- a/test-app/runtime/src/main/cpp/Timers.cpp +++ b/test-app/runtime/src/main/cpp/Timers.cpp @@ -2,19 +2,29 @@ #include "ArgConverter.h" #include "Runtime.h" #include "NativeScriptException.h" -#include -#include -#include #include "ModuleBinding.h" #include "IsolateDisposer.h" +#include "JEnv.h" +#include "JniLocalRef.h" #include "Util.h" +#include +#include /** * Overall rules when modifying this file: - * `sortedTimers_` must always be sorted by dueTime - * `sortedTimers_`. `deletedTimers_` and `stopped` modifications MUST be done while locked with the mutex - * `threadLoop` must not access anything that is not `sortedTimers_` or `stopped` or any atomic var + * Everything runs on the isolate's thread (or under its v8::Locker): there are + * no background threads and no locking. `sortedTimers_` must always be sorted + * by dueTime (stable for equal dueTimes) and in sync with `timerMap_`. + * + * Scheduling model: every scheduled timer enqueues one anonymous "due token" + * message on a dedicated Java Handler bound to this thread's Looper, at + * uptimeMillis >= the timer's due time. Timers therefore share one queue with + * Handler.post/postDelayed and interleave with Java messages in exact + * MessageQueue order. Because the Java queue is millisecond-quantized, the + * token does not name a timer: each token fires the front of `sortedTimers_` + * (the earliest due timer by exact sub-millisecond time). A token whose front + * timer is not yet due is a leftover from a cleared timer and is dropped. * ALL changes and scheduling of a TimerTask MUST be done when locked in an isolate to ensure consistency */ @@ -47,8 +57,10 @@ static double now_ms() { namespace tns { - - +jclass Timers::TIMER_HANDLER_CLASS = nullptr; +jmethodID Timers::TIMER_HANDLER_CTOR = nullptr; +jmethodID Timers::TIMER_HANDLER_POST = nullptr; +jmethodID Timers::TIMER_HANDLER_RELEASE = nullptr; void Timers::Init(v8::Isolate *isolate, v8::Local &globalObjectTemplate) { isolate_ = isolate; @@ -57,18 +69,22 @@ void Timers::Init(v8::Isolate *isolate, v8::Local &globalObj SetMethod(isolate, globalObjectTemplate, "__ns__setInterval", SetIntervalCallback, External::New(isolate, this)); SetMethod(isolate, globalObjectTemplate, "__ns__clearTimeout", ClearTimer, External::New(isolate, this)); SetMethod(isolate, globalObjectTemplate, "__ns__clearInterval", ClearTimer, External::New(isolate, this)); - auto res = pipe(fd_); - assert(res != -1); - res = fcntl(fd_[1], F_SETFL, O_NONBLOCK); - assert(res != -1); - // TODO: check success of fd - looper_ = ALooper_prepare(0); - ALooper_acquire(looper_); - ALooper_addFd(looper_, fd_[0], ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, - PumpTimerLoopCallback, this); - ALooper_wake(looper_); - watcher_ = std::thread(&Timers::threadLoop, this); - stopped = false; + + JEnv env; + if (TIMER_HANDLER_CLASS == nullptr) { + // JEnv::FindClass caches a global ref to the class + TIMER_HANDLER_CLASS = env.FindClass("com/tns/TimerHandler"); + assert(TIMER_HANDLER_CLASS != nullptr); + TIMER_HANDLER_CTOR = env.GetMethodID(TIMER_HANDLER_CLASS, "", "(J)V"); + TIMER_HANDLER_POST = env.GetMethodID(TIMER_HANDLER_CLASS, "post", "(J)V"); + TIMER_HANDLER_RELEASE = env.GetMethodID(TIMER_HANDLER_CLASS, "release", "()V"); + } + // the handler binds to the current thread's Looper, which Runtime.java + // prepares before initNativeScript on both the main and worker threads + JniLocalRef handler(env.NewObject(TIMER_HANDLER_CLASS, TIMER_HANDLER_CTOR, + reinterpret_cast(this))); + handler_ = env.NewGlobalRef(handler); + stopped_ = false; } void Timers::addTask(std::shared_ptr task) { @@ -76,42 +92,33 @@ void Timers::addTask(std::shared_ptr task) { return; } auto now = now_ms(); - task->nestingLevel_ = nesting + 1; task->queued_ = true; +#ifdef NS_TIMERS_NESTING_CLAMP + task->nestingLevel_ = nesting + 1; // theoretically this should be >5 on the spec, but we're following chromium behavior here again if (task->nestingLevel_ >= 5 && task->frequency_ < 4) { task->frequency_ = 4; task->startTime_ = now; } +#endif timerMap_.emplace(task->id_, task); - auto newTime = task->NextTime(now); - task->dueTime_ = newTime; - bool needsScheduling = true; - if (!isBufferFull.load() && task->dueTime_ <= now) { - auto result = write(fd_[1], &task->id_, sizeof(int)); - if (result != -1 || errno != EAGAIN) { - needsScheduling = false; - } else { - isBufferFull = true; - } - } - if (needsScheduling) { - { - std::lock_guard lock(mutex); - auto it = sortedTimers_.begin(); - auto dueTime = task->dueTime_; - it = std::upper_bound(sortedTimers_.begin(), sortedTimers_.end(), dueTime, - [](const double &value, - const std::shared_ptr &ref) { - return ref->dueTime > value; - }); - auto ref = std::make_shared(); - ref->dueTime = task->dueTime_; - ref->id = task->id_; - sortedTimers_.insert(it, ref); - } - taskReady.notify_one(); - } + task->dueTime_ = task->NextTime(now); + auto it = std::upper_bound(sortedTimers_.begin(), sortedTimers_.end(), task->dueTime_, + [](const double &value, const TimerReference &ref) { + return ref.dueTime > value; + }); + sortedTimers_.insert(it, TimerReference{task->id_, task->dueTime_}); + postTimer(task, now); +} + +void Timers::postTimer(const std::shared_ptr &task, double now) { + // uptimeMillis is the integer part of the same CLOCK_MONOTONIC clock as + // now_ms(). Due-now timers post at (jlong) now so they tie (and FIFO) with + // a Handler.postDelayed(0) made in the same millisecond; future timers + // post at ceil(dueTime) so the token never arrives before the due time. + auto when = task->dueTime_ <= now ? (jlong) now : (jlong) std::ceil(task->dueTime_); + JEnv env; + env.CallVoidMethod(handler_, TIMER_HANDLER_POST, when); } void Timers::removeTask(const std::shared_ptr &task) { @@ -121,76 +128,41 @@ void Timers::removeTask(const std::shared_ptr &task) { void Timers::removeTask(const int &taskId) { auto it = timerMap_.find(taskId); if (it != timerMap_.end()) { - auto wasScheduled = it->second->queued_; - it->second->Unschedule(); - timerMap_.erase(it); - // we could safely remove this block to avoid locking the main thread - // the only issue is that removing this will trigger more small locks on the main thread - { - std::lock_guard lock(mutex); - if (wasScheduled) { - // was scheduled, notify the thread so it doesn't trigger again - this->deletedTimers_.insert(taskId); - } else { - // was not scheduled, remove it to reduce memory footprint - this->deletedTimers_.erase(taskId); - } - } - } -} - -void Timers::threadLoop() { - std::unique_lock lk(mutex); - while (!stopped) { - if (!sortedTimers_.empty()) { - auto timer = sortedTimers_.at(0); - if (deletedTimers_.find(timer->id) != deletedTimers_.end()) { - sortedTimers_.erase(sortedTimers_.begin()); - deletedTimers_.erase(timer->id); - continue; - } - auto now = now_ms(); - // timer has reached its time, fire it and keep going - if (timer->dueTime <= now) { - sortedTimers_.erase(sortedTimers_.begin()); - auto result = write(fd_[1], &timer->id, sizeof(int)); - if (result == -1 && errno == EAGAIN) { - isBufferFull = true; - while (!stopped && deletedTimers_.find(timer->id) != deletedTimers_.end() && - write(fd_[1], &timer->id, sizeof(int)) == -1 && errno == EAGAIN) { - bufferFull.wait(lk); - } - } else if (isBufferFull.load() && - (sortedTimers_.empty() || sortedTimers_.at(0)->dueTime > now)) { - // we had a successful write and the next timer is not due - // mark the buffer as free to re-enable the setTimeout with 0 optimization - isBufferFull = false; + // if still scheduled, drop the sorted entry; the token already in the + // java queue will find a not-yet-due (or no) front timer and no-op + if (it->second->queued_) { + auto dueTime = it->second->dueTime_; + auto sit = std::lower_bound(sortedTimers_.begin(), sortedTimers_.end(), dueTime, + [](const TimerReference &ref, const double &value) { + return ref.dueTime < value; + }); + while (sit != sortedTimers_.end() && sit->dueTime == dueTime) { + if (sit->id == taskId) { + sortedTimers_.erase(sit); + break; } - } else { - taskReady.wait_for(lk, std::chrono::milliseconds((int) (timer->dueTime - now))); + ++sit; } - } else { - taskReady.wait(lk); } + it->second->Unschedule(); + timerMap_.erase(it); } } void Timers::Destroy() { - { - std::lock_guard lock(mutex); - if (stopped) { - return; - } - stopped = true; + if (stopped_) { + return; + } + stopped_ = true; + if (handler_ != nullptr) { + JEnv env; + env.CallVoidMethod(handler_, TIMER_HANDLER_RELEASE); + env.DeleteGlobalRef(handler_); + handler_ = nullptr; } - bufferFull.notify_one(); - taskReady.notify_all(); - watcher_.join(); - auto mainLooper = Runtime::GetMainLooper(); - ALooper_removeFd(mainLooper, fd_[0]); - close(fd_[0]); timerMap_.clear(); - ALooper_release(looper_); + sortedTimers_.clear(); + isolate_ = nullptr; } Timers::~Timers() { @@ -261,35 +233,45 @@ void Timers::SetTimer(const v8::FunctionCallbackInfo &args, bool repe } /** - * ALooper callback. - * Responsible for checking if the callback is still scheduled and entering the isolate to trigger it + * Invoked by Java TimerHandler.handleMessage on the isolate's thread, once per + * scheduled "due token". Fires the earliest due timer (exact sub-millisecond + * order), which is not necessarily the timer that enqueued this token. */ -int Timers::PumpTimerLoopCallback(int fd, int events, void *data) { - int timerId; - read(fd, &timerId, sizeof(int)); - - auto thiz = static_cast(data); - auto isolate = thiz->isolate_; - if (thiz->stopped || isolate == nullptr || isolate->IsDead()) { - return 0; +void Timers::FireTimer() { + auto isolate = isolate_; + if (stopped_ || isolate == nullptr || isolate->IsDead()) { + return; } // thread safety is important! - v8::Locker locker(thiz->isolate_); + v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handleScope(isolate); - auto it = thiz->timerMap_.find(timerId); - if (it != thiz->timerMap_.end()) { + if (sortedTimers_.empty()) { + // leftover token of a cleared timer + return; + } + auto ref = sortedTimers_.front(); + if (ref.dueTime > now_ms()) { + // the earliest timer isn't due yet, so this token is a leftover from a + // cleared timer; the front timer's own token will arrive at its due time + return; + } + sortedTimers_.erase(sortedTimers_.begin()); + auto it = timerMap_.find(ref.id); + if (it != timerMap_.end()) { auto task = it->second; // task is no longer in queue to be executed task->queued_ = false; - thiz->nesting = task->nestingLevel_; +#ifdef NS_TIMERS_NESTING_CLAMP + nesting = task->nestingLevel_; +#endif if (task->repeats_) { // the reason we're doing this in kind of a convoluted way is to follow more closely the chromium implementation than the node implementation // imagine an interval of 1000ms // node's setInterval drifts slightly (1000, 2001, 3001, 4002, some busy work 5050, 6050) // chromium will be consistent: (1000, 2001, 3000, 4000, some busy work 5050, 6000) task->startTime_ = task->dueTime_; - thiz->addTask(task); + addTask(task); } v8::Local cb = task->callback_.Get(isolate); Runtime* runtime = Runtime::GetRuntime(isolate); @@ -309,10 +291,12 @@ int Timers::PumpTimerLoopCallback(int fd, int events, void *data) { // task is not queued, so it's either a setTimeout or a cleared setInterval // ensure we remove it if (!task->queued_) { - thiz->removeTask(task); + removeTask(task); } - thiz->nesting = 0; +#ifdef NS_TIMERS_NESTING_CLAMP + nesting = 0; +#endif if (tc.HasCaught()) { NativeScriptException(tc).ReThrowToJava(); @@ -320,8 +304,6 @@ int Timers::PumpTimerLoopCallback(int fd, int events, void *data) { } - thiz->bufferFull.notify_one(); - return 1; } void Timers::InitStatic(v8::Isolate* isolate, v8::Local globalObjectTemplate) { @@ -332,4 +314,21 @@ void Timers::InitStatic(v8::Isolate* isolate, v8::Local glob }; -NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::InitStatic); \ No newline at end of file +NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::InitStatic); + +extern "C" JNIEXPORT void JNICALL Java_com_tns_TimerHandler_nativeFireTimer( + JNIEnv *env, jclass clazz, jlong timersPtr) { + try { + reinterpret_cast(timersPtr)->FireTimer(); + } catch (tns::NativeScriptException &e) { + e.ReThrowToJava(); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + tns::NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(); + } catch (...) { + tns::NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(); + } +} diff --git a/test-app/runtime/src/main/cpp/Timers.h b/test-app/runtime/src/main/cpp/Timers.h index 35fd9485d..f9bf24ebe 100644 --- a/test-app/runtime/src/main/cpp/Timers.h +++ b/test-app/runtime/src/main/cpp/Timers.h @@ -1,13 +1,16 @@ #ifndef TEST_APP_TIMERS_H #define TEST_APP_TIMERS_H -#include +#include #include "v8.h" #include "ObjectManager.h" -#include "condition_variable" -#include "thread" #include "robin_hood.h" +// Define NS_TIMERS_NESTING_CLAMP to enable the HTML-spec/Chromium nesting +// clamp (>=5 nested timer levels -> 4ms minimum delay). Off by default: the +// clamp exists to keep browser pages from spinning the event loop, which is +// rarely the desired behavior in a native app. + namespace tns { /** * A Timer Task @@ -43,7 +46,9 @@ namespace tns { queued_ = false; } +#ifdef NS_TIMERS_NESTING_CLAMP int nestingLevel_ = 0; +#endif v8::Isolate *isolate_; v8::Persistent callback_; std::shared_ptr>>> args_; @@ -52,7 +57,7 @@ namespace tns { * this helper parameter is used in the following way: * task scheduled means queued_ = true * this is set to false right before the callback is executed - * if this is false then it's not on the background thread queue + * if this is false then it's not on the message queue to be executed */ bool queued_ = false; double frequency_ = 0; @@ -70,7 +75,7 @@ namespace tns { public: /** * Initializes the global functions setTimeout, setInterval, clearTimeout and clearInterval - * also creates helper threads and binds the timers to the executing thread + * also creates the dedicated Java Handler bound to the executing thread's Looper * @param isolate target isolate * @param globalObjectTemplate global template */ @@ -79,19 +84,25 @@ namespace tns { static void InitStatic(v8::Isolate* isolate, v8::Local globalObjectTemplate); /** - * Disposes the timers. This will clear all references and stop all thread. + * Disposes the timers. This will clear all references and remove all + * pending timer messages from the Java message queue. * MUST be called in the same thread Init was called - * This methods blocks until the threads are stopped. * This method doesn't need to be called most of the time as it's called on object destruction * Reusing this class is not advised */ void Destroy(); /** - * Calls Destruct + * Calls Destroy */ ~Timers(); + /** + * Fires the earliest due timer, if any (invoked by Java + * TimerHandler.handleMessage once per scheduled "due token") + */ + void FireTimer(); + private: static void SetTimeoutCallback(const v8::FunctionCallbackInfo &args); @@ -101,34 +112,37 @@ namespace tns { static void ClearTimer(const v8::FunctionCallbackInfo &args); - void threadLoop(); - - static int PumpTimerLoopCallback(int fd, int events, void *data); - void addTask(std::shared_ptr task); void removeTask(const std::shared_ptr &task); void removeTask(const int &taskId); + void postTimer(const std::shared_ptr &task, double now); + v8::Isolate *isolate_ = nullptr; - ALooper *looper_; int currentTimerId = 0; +#ifdef NS_TIMERS_NESTING_CLAMP int nesting = 0; +#endif // stores the map of timer tasks robin_hood::unordered_map> timerMap_; - std::vector> sortedTimers_; - // sets are faster than vector iteration - // so we use this to avoid redundant isolate locks and we don't care about the - // background thread lost cycles - std::set deletedTimers_; - int fd_[2]; - std::atomic_bool isBufferFull = false; - std::condition_variable taskReady; - std::condition_variable bufferFull; - std::mutex mutex; - std::thread watcher_; - bool stopped = false; + // scheduled timers sorted by exact (sub-millisecond) dueTime, stable for + // equal dueTimes. Only ever touched under the isolate lock, no mutex. + // The Java message queue is millisecond-quantized, so this preserves the + // relative order of JS timers; each Java message is an anonymous token + // that fires the front of this list. + std::vector sortedTimers_; + // global ref to the dedicated com.tns.TimerHandler for this isolate's thread + jobject handler_ = nullptr; + bool stopped_ = false; + + // process-wide JNI cache, written once on the first Timers::Init (main + // runtime), which happens-before any worker thread is spawned + static jclass TIMER_HANDLER_CLASS; + static jmethodID TIMER_HANDLER_CTOR; + static jmethodID TIMER_HANDLER_POST; + static jmethodID TIMER_HANDLER_RELEASE; }; } diff --git a/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp b/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp index 710079bab..a0aa9bddb 100644 --- a/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp +++ b/test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp @@ -10,6 +10,12 @@ namespace tns { URLSearchParamsImpl::URLSearchParamsImpl(ada::url_search_params params) : params_(params) {} URLSearchParamsImpl *URLSearchParamsImpl::GetPointer(v8::Local object) { + // A foreign receiver (e.g. entries.call({})) has no internal field, and + // reading one that is not there is undefined behavior, so check first + // and report not-a-URLSearchParams instead. + if (object->InternalFieldCount() < 1) { + return nullptr; + } auto ptr = object->GetAlignedPointerFromInternalField(0); if (ptr == nullptr) { return nullptr; @@ -30,9 +36,11 @@ namespace tns { ArgConverter::ConvertToV8String(isolate, "delete"), v8::FunctionTemplate::New(isolate, Delete)); - tmpl->Set( - ArgConverter::ConvertToV8String(isolate, "entries"), - v8::FunctionTemplate::New(isolate, Entries)); + // Capture the entries template so it can double as the default @@iterator + // (registered below), matching the browser identity + // URLSearchParams.prototype[Symbol.iterator] === entries. + auto entriesTmpl = v8::FunctionTemplate::New(isolate, Entries); + tmpl->Set(ArgConverter::ConvertToV8String(isolate, "entries"), entriesTmpl); tmpl->Set( ArgConverter::ConvertToV8String(isolate, "forEach"), @@ -75,10 +83,411 @@ namespace tns { tmpl->Set(ArgConverter::ConvertToV8String(isolate, "values"), v8::FunctionTemplate::New(isolate, &Values)); + // Declare the type as iterable by exposing + // @@iterator. It aliases entries() (which returns a genuine ES iterator), + // so `for..of`, spread, and `new URLSearchParams(otherParams)` (the spec + // sequence/copy form) all work. Non-enumerable, to match the browser + // prototype shape. + tmpl->Set(v8::Symbol::GetIterator(isolate), entriesTmpl, + v8::PropertyAttribute::DontEnum); return ctorTmpl; } + // Spec-compliant URLSearchParams init (record / sequence / primitive). The + // dispatch mirrors the WebIDL union conversion to + // `(sequence> or record or USVString)` + // that browsers/Node apply (https://url.spec.whatwg.org/#interface-urlsearchparams): + // object with @@iterator -> sequence (Array, Map, Set, URLSearchParams, generator) + // object without @@iterator-> record (own enumerable keys, in order; a Symbol key throws) + // other primitive / null -> USVString (number/boolean/bigint/null -> string; Symbol throws) + // undefined / missing -> empty (the IDL default "") + namespace { + void ThrowTypeError(v8::Isolate *isolate, const char *message) { + isolate->ThrowException( + v8::Exception::TypeError(ArgConverter::ConvertToV8String(isolate, message))); + } + + // --- Live iterators for entries()/keys()/values()/@@iterator. --- + // Spec URLSearchParams iterators are *live*: they reflect append()/delete() + // performed after the iterator is created (and mid-iteration). Each is backed + // by a small hidden state object — { src: the URLSearchParams, idx: cursor, + // knd: which projection } — captured as next()'s Data. No snapshot, and no + // bespoke iterator class/finalizer: the source is kept alive by the state + // reference and collected normally with the iterator. next() re-reads the + // params on every call, so duplicate keys keep their own values too. + enum IteratorKind { ITER_KEYS = 0, ITER_VALUES = 1, ITER_ENTRIES = 2 }; + + const char *const kStateSource = "src"; + const char *const kStateIndex = "idx"; + const char *const kStateKind = "knd"; + const char *const kStateIterator = "itr"; + + void IteratorSelf(const v8::FunctionCallbackInfo &args) { + // An iterator is itself iterable (iterator[@@iterator]() === iterator), + // so `for..of params.entries()` works. + args.GetReturnValue().Set(args.This()); + } + + void IteratorNext(const v8::FunctionCallbackInfo &args) { + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + auto state = args.Data().As(); + + // WebIDL brand check: next() must be invoked on the iterator it was + // created on, so a detached next() throws instead of advancing the + // captured state. + v8::Local iteratorValue; + if (!state->Get(context, ArgConverter::ConvertToV8String(isolate, kStateIterator)) + .ToLocal(&iteratorValue) || + !iteratorValue->StrictEquals(args.This())) { + ThrowTypeError(isolate, "Illegal invocation"); + return; + } + + auto indexKey = ArgConverter::ConvertToV8String(isolate, kStateIndex); + v8::Local sourceValue; + v8::Local indexValue; + v8::Local kindValue; + if (!state->Get(context, ArgConverter::ConvertToV8String(isolate, kStateSource)) + .ToLocal(&sourceValue) || + !state->Get(context, indexKey).ToLocal(&indexValue) || + !state->Get(context, ArgConverter::ConvertToV8String(isolate, kStateKind)) + .ToLocal(&kindValue)) { + return; + } + auto index = indexValue->Uint32Value(context).FromMaybe(0u); + auto kind = kindValue->Int32Value(context).FromMaybe(ITER_ENTRIES); + + auto result = v8::Object::New(isolate); + auto valueKey = ArgConverter::ConvertToV8String(isolate, "value"); + auto doneKey = ArgConverter::ConvertToV8String(isolate, "done"); + + URLSearchParamsImpl *ptr = sourceValue->IsObject() + ? URLSearchParamsImpl::GetPointer(sourceValue.As()) : nullptr; + if (ptr == nullptr || index >= ptr->GetURLSearchParams()->size()) { + result->Set(context, valueKey, v8::Undefined(isolate)).Check(); + result->Set(context, doneKey, v8::True(isolate)).Check(); + args.GetReturnValue().Set(result); + return; + } + + auto pair = (*ptr->GetURLSearchParams())[index]; + v8::Local value; + if (kind == ITER_KEYS) { + value = ArgConverter::ConvertToV8String(isolate, pair.first); + } else if (kind == ITER_VALUES) { + value = ArgConverter::ConvertToV8String(isolate, pair.second); + } else { + v8::Local kv[] = { + ArgConverter::ConvertToV8String(isolate, pair.first), + ArgConverter::ConvertToV8String(isolate, pair.second), + }; + value = v8::Array::New(isolate, kv, 2); + } + state->Set(context, indexKey, v8::Integer::NewFromUnsigned(isolate, index + 1)).Check(); + result->Set(context, valueKey, value).Check(); + result->Set(context, doneKey, v8::False(isolate)).Check(); + args.GetReturnValue().Set(result); + } + + // Sets `args`' return value to a fresh live iterator of `kind` over + // `args.This()` (the URLSearchParams). The state object is never exposed to + // JS — only the iterator (with next() and @@iterator) is returned. + void ReturnLiveIterator(const v8::FunctionCallbackInfo &args, int kind) { + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + + // WebIDL brand check: entries()/keys()/values() require a genuine + // URLSearchParams receiver, so e.g. entries.call({}) throws. + if (URLSearchParamsImpl::GetPointer(args.This()) == nullptr) { + ThrowTypeError(isolate, "Illegal invocation"); + return; + } + + auto state = v8::Object::New(isolate); + state->Set(context, ArgConverter::ConvertToV8String(isolate, kStateSource), + args.This()).Check(); + state->Set(context, ArgConverter::ConvertToV8String(isolate, kStateIndex), + v8::Integer::NewFromUnsigned(isolate, 0)).Check(); + state->Set(context, ArgConverter::ConvertToV8String(isolate, kStateKind), + v8::Integer::New(isolate, kind)).Check(); + + v8::Local nextFn; + v8::Local selfFn; + if (!v8::Function::New(context, IteratorNext, state).ToLocal(&nextFn) || + !v8::Function::New(context, IteratorSelf).ToLocal(&selfFn)) { + return; + } + + auto iterator = v8::Object::New(isolate); + // Recorded in the hidden state so next() can brand-check its receiver. + state->Set(context, ArgConverter::ConvertToV8String(isolate, kStateIterator), + iterator).Check(); + iterator->Set(context, ArgConverter::ConvertToV8String(isolate, "next"), nextFn).Check(); + iterator->Set(context, v8::Symbol::GetIterator(isolate), selfFn).Check(); + iterator->DefineOwnProperty(context, v8::Symbol::GetToStringTag(isolate), + ArgConverter::ConvertToV8String(isolate, + "URLSearchParams Iterator"), + v8::PropertyAttribute::DontEnum).Check(); + args.GetReturnValue().Set(iterator); + } + + // Coerce a JS value to a UTF-8 string via the USVString conversion the + // URLSearchParams init algorithm applies to every name/value. Returns + // false if the conversion threw — a Symbol value, or a user toString that + // throws — leaving the JS exception pending so the caller can stop and + // let it propagate (the spec throws here rather than dropping the entry). + // No further V8 API may be called once that happens. + bool ValueToString(v8::Local context, v8::Local value, + std::string &out) { + v8::Local str; + if (!value->ToString(context).ToLocal(&str)) { + return false; + } + out = ArgConverter::ConvertToString(str); + return true; + } + + // Opens a JS sync iterator: invokes `iterMethod` (the resolved @@iterator) + // with `receiver` as the `this` value, requires the result to be an object, + // and reads its callable `next` method. On any deviation the matching JS + // TypeError is thrown (or left pending, if a user callback threw) and the + // function returns false. + bool OpenIterator(v8::Local context, v8::Local receiver, + v8::Local iterMethod, v8::Local &outIterator, + v8::Local &outNext) { + auto isolate = context->GetIsolate(); + v8::Local iteratorValue; + if (!iterMethod->Call(context, receiver, 0, nullptr).ToLocal(&iteratorValue)) { + return false; + } + if (!iteratorValue->IsObject()) { + ThrowTypeError(isolate, "Result of the Symbol.iterator method is not an object"); + return false; + } + outIterator = iteratorValue.As(); + v8::Local nextValue; + if (!outIterator->Get(context, ArgConverter::ConvertToV8String(isolate, "next")) + .ToLocal(&nextValue)) { + return false; + } + if (!nextValue->IsFunction()) { + ThrowTypeError(isolate, "Iterator has no 'next' method"); + return false; + } + outNext = nextValue.As(); + return true; + } + + // Advances `iterator` once. On a normal step writes the yielded value to + // `outValue` and leaves `outDone` false; at completion sets `outDone` true. + // Returns false (exception pending) if next() threw or yielded a non-object + // result. `next` is captured once by the caller, per the iterator protocol. + bool StepIterator(v8::Local context, v8::Local iterator, + v8::Local next, v8::Local &outValue, + bool &outDone) { + auto isolate = context->GetIsolate(); + v8::Local resultValue; + if (!next->Call(context, iterator, 0, nullptr).ToLocal(&resultValue)) { + return false; + } + if (!resultValue->IsObject()) { + ThrowTypeError(isolate, "Iterator result is not an object"); + return false; + } + auto result = resultValue.As(); + v8::Local doneValue; + if (!result->Get(context, ArgConverter::ConvertToV8String(isolate, "done")) + .ToLocal(&doneValue)) { + return false; + } + outDone = doneValue->BooleanValue(isolate); + if (outDone) { + return true; + } + return result->Get(context, ArgConverter::ConvertToV8String(isolate, "value")) + .ToLocal(&outValue); + } + + // ES IteratorClose for an abrupt (error) completion: call the iterator's + // `return` method, if it has one, and discard both its result and any error + // it raises — the triggering exception wins (spec discards `return`'s + // completion on a throw). Generators with `finally` and resource-backed + // iterables rely on this to release state when conversion fails partway. + // Must be called with no pending JS exception: the caller captures and + // re-throws the triggering exception around this call. + void CloseIterator(v8::Local context, v8::Local iterator) { + auto isolate = context->GetIsolate(); + v8::TryCatch tryCatch(isolate); + v8::Local returnMethod; + if (iterator->Get(context, ArgConverter::ConvertToV8String(isolate, "return")) + .ToLocal(&returnMethod) && returnMethod->IsFunction()) { + v8::Local ignored; + (void) returnMethod.As()->Call(context, iterator, 0, nullptr) + .ToLocal(&ignored); + } + // tryCatch swallows anything return() throws when it goes out of scope. + } + + // Materializes one inner element of a sequence init into its strings. Per + // spec each element is itself converted to `sequence`, whose first + // step ("convert to sequence") throws a TypeError when Type(V) is not + // Object. So a non-Object (primitive) element — including a primitive string — + // throws; it is NOT boxed (boxing would wrongly accept ["ab"] as the pair + // ("a","b")). Iterable objects (Array, Set, a String *object*) are materialized + // by driving their @@iterator. If an element fails to coerce, the inner + // iterator is closed before the exception propagates. + bool MaterializeInnerSequence(v8::Local context, v8::Local value, + std::vector &out) { + auto isolate = context->GetIsolate(); + // Per WebIDL "convert to sequence": if Type(V) is not Object, throw a + // TypeError. Primitives (including strings) are NOT boxed here — boxing + // would wrongly accept e.g. ["ab"] as the pair ("a","b") instead of + // throwing. A String/Set/Array *object* element is fine (it is an Object). + if (!value->IsObject()) { + ThrowTypeError(isolate, "URLSearchParams init sequence element is not iterable"); + return false; + } + v8::Local object = value.As(); + v8::Local iterMethod; + if (!object->Get(context, v8::Symbol::GetIterator(isolate)).ToLocal(&iterMethod)) { + return false; + } + if (!iterMethod->IsFunction()) { + ThrowTypeError(isolate, "URLSearchParams init sequence element is not iterable"); + return false; + } + v8::Local iterator; + v8::Local next; + if (!OpenIterator(context, object, iterMethod.As(), iterator, next)) { + return false; + } + while (true) { + v8::Local element; + bool done = false; + if (!StepIterator(context, iterator, next, element, done)) { + return false; // next() threw: the iterator is already finished + } + if (done) { + break; + } + // Capture a conversion failure, then close the iterator outside the + // TryCatch's scope so the close runs with a clean isolate and the + // original exception is re-thrown (not swallowed by this handler). + v8::Local caught; + { + v8::TryCatch tryCatch(isolate); + std::string str; + if (ValueToString(context, element, str)) { + out.push_back(std::move(str)); + } else { + caught = tryCatch.Exception(); + tryCatch.Reset(); + } + } + if (!caught.IsEmpty()) { + CloseIterator(context, iterator); + isolate->ThrowException(caught); + return false; + } + } + return true; + } + + // Sequence init form: iterate the value via its @@iterator (already resolved + // by the caller) and append each [name, value] pair. Covers Array, Map, Set, + // another URLSearchParams and generators uniformly. A pair whose length is + // not exactly 2 throws a TypeError; a name/value that cannot be coerced to a + // string aborts with the JS exception left pending. On either of those two + // failures the source iterator is closed first (ES IteratorClose). A throw + // from the stepping itself (next(), or reading the result's done/value) + // intentionally does NOT close the iterator: the ES iterator protocol marks + // the iterator done on such a throw (IteratorStepValue), and IteratorClose + // is skipped for a done iterator, which is also how for..of behaves. + bool BuildFromSequence(v8::Local context, v8::Local object, + v8::Local iterMethod, ada::url_search_params ¶ms) { + auto isolate = context->GetIsolate(); + v8::Local iterator; + v8::Local next; + if (!OpenIterator(context, object, iterMethod, iterator, next)) { + return false; + } + while (true) { + v8::Local entry; + bool done = false; + if (!StepIterator(context, iterator, next, entry, done)) { + return false; // next() threw: the iterator is already finished + } + if (done) { + break; + } + // Convert and append under a TryCatch; on failure capture the + // exception, then close the source iterator outside the handler's + // scope and re-throw (so the close runs clean and the error survives). + v8::Local caught; + { + v8::TryCatch tryCatch(isolate); + std::vector pair; + bool built = MaterializeInnerSequence(context, entry, pair); + if (built && pair.size() != 2) { + ThrowTypeError(isolate, + "Failed to construct 'URLSearchParams': Sequence initializer " + "must only contain pair elements"); + } + if (tryCatch.HasCaught()) { + caught = tryCatch.Exception(); + tryCatch.Reset(); + } else { + params.append(pair[0], pair[1]); + } + } + if (!caught.IsEmpty()) { + CloseIterator(context, iterator); + isolate->ThrowException(caught); + return false; + } + } + return true; + } + + // Record init form: a plain object of name -> value, iterated in own + // enumerable key order ([[OwnPropertyKeys]]: strings, then symbols). Per + // WebIDL record conversion every enumerable key is converted to a + // USVString, so an own enumerable Symbol key throws a TypeError + // (ValueToString below raises it); symbols are NOT silently skipped. A + // key or value that cannot be coerced to a string aborts with the JS + // exception left pending. + bool BuildFromRecord(v8::Local context, v8::Local object, + ada::url_search_params ¶ms) { + auto filter = v8::PropertyFilter::ONLY_ENUMERABLE; + v8::Local keys; + if (!object->GetOwnPropertyNames(context, filter, + v8::KeyConversionMode::kConvertToString).ToLocal(&keys)) { + return false; + } + auto length = keys->Length(); + for (uint32_t i = 0; i < length; i++) { + v8::Local key; + if (!keys->Get(context, i).ToLocal(&key)) { + return false; + } + v8::Local value; + if (!object->Get(context, key).ToLocal(&value)) { + return false; + } + std::string keyStr; + std::string valueStr; + if (!ValueToString(context, key, keyStr) || + !ValueToString(context, value, valueStr)) { + return false; + } + params.append(keyStr, valueStr); + } + return true; + } + } + void URLSearchParamsImpl::Ctor(const v8::FunctionCallbackInfo &args) { auto value = args[0]; auto isolate = args.GetIsolate(); @@ -88,11 +497,47 @@ namespace tns { ada::url_search_params params; if (value->IsString()) { + // ada strips a single leading '?' (constructor step), then runs the + // urlencoded string parser. params = ada::url_search_params(ArgConverter::ConvertToString(value.As())); - } else if (value->IsObject()) { - params = ada::url_search_params( - ArgConverter::ConvertToString(value->ToString(context).ToLocalChecked())); } + // Dispatch the record/sequence/primitive init forms via the helpers above. + // A failed coercion leaves a pending exception; return so V8 throws it. + else if (value->IsObject()) { + auto object = value.As(); + v8::Local iterMethod; + if (!object->Get(context, v8::Symbol::GetIterator(isolate)).ToLocal(&iterMethod)) { + return; // the @@iterator getter threw + } + if (iterMethod->IsNullOrUndefined()) { + if (!BuildFromRecord(context, object, params)) { + return; + } + } else if (iterMethod->IsFunction()) { + if (!BuildFromSequence(context, object, iterMethod.As(), params)) { + return; + } + } else { + // Per WebIDL GetMethod, a present-but-non-callable @@iterator is a + // TypeError rather than a silent fall-back to the record form. + ThrowTypeError(isolate, "URLSearchParams init Symbol.iterator is not a function"); + return; + } + } else if (!value->IsUndefined()) { + // Other primitive (number / boolean / bigint / symbol / null): coerce + // to a USVString and run the urlencoded string parser. The WebIDL + // union conversion has no null special case (the union is not + // nullable and a record is not a dictionary), so + // new URLSearchParams(null) parses the string "null", as the + // reference implementation does. A Symbol cannot be converted and + // throws here, matching the spec. + std::string init; + if (!ValueToString(context, value, init)) { + return; + } + params = ada::url_search_params(init); + } + // undefined / missing -> the IDL default "" -> leave params empty auto searchParams = new URLSearchParamsImpl(params); @@ -107,49 +552,59 @@ namespace tns { void URLSearchParamsImpl::Append(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { + // WebIDL brand check: every member requires a genuine URLSearchParams + // receiver (same as entries()/keys()/values()). + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } - auto key = ArgConverter::ConvertToString(args[0].As()); - auto value = ArgConverter::ConvertToString(args[1].As()); - ptr->GetURLSearchParams()->append(key.c_str(), value.c_str()); + // Both arguments are USVStrings (url.bs:3860); a Symbol or throwing + // toString leaves the pending exception and aborts. + auto context = args.GetIsolate()->GetCurrentContext(); + std::string key; + std::string value; + if (!ValueToString(context, args[0], key) || + !ValueToString(context, args[1], value)) { + return; + } + ptr->GetURLSearchParams()->append(key, value); } void URLSearchParamsImpl::Delete(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } - auto key = ArgConverter::ConvertToString(args[0].As()); - ptr->GetURLSearchParams()->remove(key.c_str()); - } - - void URLSearchParamsImpl::Entries(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); + // The name is a USVString (url.bs:3861): coerce it like the optional + // value below instead of assuming a string (a Symbol or throwing + // toString leaves the pending exception and aborts). + std::string key; + if (!ValueToString(args.GetIsolate()->GetCurrentContext(), args[0], key)) { return; } - - auto keys = ptr->GetURLSearchParams()->get_keys(); - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - auto value = ptr->GetURLSearchParams()->get(keyValue).value(); - v8::Local values[] = { - ArgConverter::ConvertToV8String(isolate, keyValue.data()), - ArgConverter::ConvertToV8String(isolate, value.data()), - }; - ret->Set(context, i++, v8::Array::New(isolate, values, 2)); + // Spec delete(name, value): when the value argument is given, remove only + // tuples matching BOTH name and value (url.bs:4000). The value is a + // USVString, so coerce via ValueToString (number/boolean -> string; a + // Symbol or throwing toString leaves the pending exception and aborts). + // An explicit `undefined` value is treated as omitted (name-only), matching + // WPT "Two-argument delete() respects undefined as second arg"; only a + // present, non-undefined value takes the value-filtered path. + if (args.Length() > 1 && !args[1]->IsUndefined()) { + std::string value; + if (!ValueToString(args.GetIsolate()->GetCurrentContext(), args[1], value)) { + return; // coercion threw (Symbol / throwing toString) } - + ptr->GetURLSearchParams()->remove(key, value); + } else { + ptr->GetURLSearchParams()->remove(key.c_str()); } - args.GetReturnValue().Set(ret); + } + + // entries()/keys()/values() return live ES iterators (per spec they are + // iterators, not arrays). Reading the params by index per next() also keeps + // each occurrence of a repeated key paired with its own value. + void URLSearchParamsImpl::Entries(const v8::FunctionCallbackInfo &args) { + ReturnLiveIterator(args, ITER_ENTRIES); } void URLSearchParamsImpl::ForEach(const v8::FunctionCallbackInfo &args) { @@ -157,6 +612,7 @@ namespace tns { auto isolate = args.GetIsolate(); auto context = isolate->GetCurrentContext(); if (ptr == nullptr) { + ThrowTypeError(isolate, "Illegal invocation"); return; } auto callback = args[0].As(); @@ -188,16 +644,23 @@ namespace tns { URLSearchParamsImpl *ptr = GetPointer(args.This()); auto isolate = args.GetIsolate(); if (ptr == nullptr) { - args.GetReturnValue().SetUndefined(); + ThrowTypeError(isolate, "Illegal invocation"); return; } - auto key = args[0].As(); - auto value = ptr->GetURLSearchParams()->get(ArgConverter::ConvertToString(key)); + // The name is a USVString (url.bs:3862); a Symbol or throwing toString + // leaves the pending exception and aborts. + std::string key; + if (!ValueToString(isolate->GetCurrentContext(), args[0], key)) { + return; + } + auto value = ptr->GetURLSearchParams()->get(key); if (value.has_value()) { auto ret = ArgConverter::ConvertToV8String(isolate, std::string(value.value())); args.GetReturnValue().Set(ret); } else { - args.GetReturnValue().SetUndefined(); + // Spec: get() returns null for a missing name (url.bs:4016), not + // undefined. + args.GetReturnValue().SetNull(); } } @@ -206,11 +669,16 @@ namespace tns { auto isolate = args.GetIsolate(); auto context = isolate->GetCurrentContext(); if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); + ThrowTypeError(isolate, "Illegal invocation"); + return; + } + // The name is a USVString (url.bs:3863); a Symbol or throwing toString + // leaves the pending exception and aborts. + std::string key; + if (!ValueToString(context, args[0], key)) { return; } - auto key = args[0].As(); - auto values = ptr->GetURLSearchParams()->get_all(ArgConverter::ConvertToString(key)); + auto values = ptr->GetURLSearchParams()->get_all(key); auto ret = v8::Array::New(isolate, values.size()); size_t i = 0; for (auto item: values) { @@ -222,60 +690,65 @@ namespace tns { void URLSearchParamsImpl::Has(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { - args.GetReturnValue().Set(false); + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } - auto key = args[0].As(); - auto value = ptr->GetURLSearchParams()->has(ArgConverter::ConvertToString(key)); - - args.GetReturnValue().Set(value); - } - - void URLSearchParamsImpl::Keys(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); + // The name is a USVString (url.bs:3864): coerce it like the optional + // value below instead of assuming a string (a Symbol or throwing + // toString leaves the pending exception and aborts). + std::string key; + if (!ValueToString(args.GetIsolate()->GetCurrentContext(), args[0], key)) { return; } - - auto keys = ptr->GetURLSearchParams()->get_keys(); - - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - ret->Set(context, i++, ArgConverter::ConvertToV8String(isolate, keyValue.data())); + // Spec has(name, value): when the value argument is given, return true + // only for a tuple matching BOTH name and value (url.bs:4028). The value + // is a USVString, so coerce via ValueToString (number/boolean -> string; + // a Symbol or throwing toString leaves the pending exception and aborts). + bool value; + // An explicit `undefined` value is treated as omitted (name-only), matching + // WPT "Two-argument has() respects undefined as second arg"; only a present, + // non-undefined value takes the value-filtered path. + if (args.Length() > 1 && !args[1]->IsUndefined()) { + std::string filter; + if (!ValueToString(args.GetIsolate()->GetCurrentContext(), args[1], filter)) { + return; // coercion threw (Symbol / throwing toString) } - + value = ptr->GetURLSearchParams()->has(key, filter); + } else { + value = ptr->GetURLSearchParams()->has(key); } - args.GetReturnValue().Set(ret); + args.GetReturnValue().Set(value); + } + + // Live ES iterator (see Entries above). + void URLSearchParamsImpl::Keys(const v8::FunctionCallbackInfo &args) { + ReturnLiveIterator(args, ITER_KEYS); } void URLSearchParamsImpl::Set(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } - auto key = args[0].As(); - auto value = args[1].As(); - - ptr->GetURLSearchParams()->set( - ArgConverter::ConvertToString(key), - ArgConverter::ConvertToString(value) - ); + // Both arguments are USVStrings (url.bs:3865); a Symbol or throwing + // toString leaves the pending exception and aborts. + auto context = args.GetIsolate()->GetCurrentContext(); + std::string key; + std::string value; + if (!ValueToString(context, args[0], key) || + !ValueToString(context, args[1], value)) { + return; + } + ptr->GetURLSearchParams()->set(key, value); } void URLSearchParamsImpl::GetSize(v8::Local property, const v8::PropertyCallbackInfo &info) { URLSearchParamsImpl *ptr = GetPointer(info.This()); if (ptr == nullptr) { - info.GetReturnValue().Set(0); + ThrowTypeError(info.GetIsolate(), "Illegal invocation"); return; } @@ -287,6 +760,7 @@ namespace tns { void URLSearchParamsImpl::Sort(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } ptr->GetURLSearchParams()->sort(); @@ -295,7 +769,7 @@ namespace tns { void URLSearchParamsImpl::ToString(const v8::FunctionCallbackInfo &args) { URLSearchParamsImpl *ptr = GetPointer(args.This()); if (ptr == nullptr) { - args.GetReturnValue().SetEmptyString(); + ThrowTypeError(args.GetIsolate(), "Illegal invocation"); return; } auto isolate = args.GetIsolate(); @@ -308,34 +782,9 @@ namespace tns { args.GetReturnValue().Set(ret); } + // Live ES iterator (see Entries above). void URLSearchParamsImpl::Values(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); - return; - } - - auto keys = ptr->GetURLSearchParams()->get_keys(); - - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto value = ptr->GetURLSearchParams()->get(key.value()); - if (value.has_value()) { - ret->Set(context, i++, - ArgConverter::ConvertToV8String(isolate, std::string(value.value()))); - } - - } - - } - args.GetReturnValue().Set(ret); - + ReturnLiveIterator(args, ITER_VALUES); } diff --git a/test-app/runtime/src/main/cpp/WorkerInspectorClient.cpp b/test-app/runtime/src/main/cpp/WorkerInspectorClient.cpp new file mode 100644 index 000000000..26cefa5e1 --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerInspectorClient.cpp @@ -0,0 +1,330 @@ +#include "WorkerInspectorClient.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#include "JsV8InspectorClient.h" +#include "Runtime.h" + +using namespace v8; +using namespace v8_inspector; + +namespace tns { + +namespace { + +StringView Make8BitStringView(const std::string& value) { + return StringView(reinterpret_cast(value.data()), value.size()); +} + +// Pure C++ (no V8 handles): legal from the Channel callbacks regardless of +// what scopes the inspector entered before calling them. +std::string ToUtf8String(const StringView& view) { + return v8_inspector::toString16(view).utf8(); +} + +} // namespace + +WorkerInspectorClient::WorkerInspectorClient(int workerId, Isolate* isolate, ALooper* workerLooper, + const std::string& url) + : workerId_(workerId), + sessionId_("NS_WORKER_" + std::to_string(workerId)), + targetId_("ns-worker-" + std::to_string(workerId)), + url_(url), + isolate_(isolate), + workerLooper_(workerLooper) { + // Wakes the worker looper when CDP messages arrive on the socket thread; + // same mechanism as the worker's message inbox (ConcurrentQueue). + eventFd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (eventFd_ != -1 && workerLooper_ != nullptr && + ALooper_addFd(workerLooper_, eventFd_, ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, + WorkerInspectorClient::InspectorMessagesCallback, this) == 1) { + ALooper_acquire(workerLooper_); + } else { + if (eventFd_ != -1) { + close(eventFd_); + eventFd_ = -1; + } + workerLooper_ = nullptr; + } + + v8::Locker locker(isolate_); + Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope(isolate_); + Local context = Runtime::GetRuntime(isolate_)->GetContext(); + + inspector_ = V8Inspector::create(isolate_, this); + // Name the context after the worker script: the DevTools console context + // selector labels entries with the context's name (or its origin as a + // fallback) — with neither set the dropdown rows are blank and + // unselectable. + V8ContextInfo contextInfo(context, contextGroupId, Make8BitStringView(url_)); + contextInfo.origin = Make8BitStringView(url_); + inspector_->contextCreated(contextInfo); + context_.Reset(isolate_, context); + session_ = inspector_->connect(contextGroupId, this, {}); +} + +WorkerInspectorClient::~WorkerInspectorClient() { + dying_ = true; + + if (eventFd_ != -1) { + // Runs on the worker thread, so unregistering from its own looper is + // safe (same rule as ConcurrentQueue::Terminate). + ALooper_removeFd(workerLooper_, eventFd_); + close(eventFd_); + eventFd_ = -1; + ALooper_release(workerLooper_); + } + + v8::Locker locker(isolate_); + Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope(isolate_); + if (session_ != nullptr) { + session_->resume(); + session_.reset(); + } + inspector_.reset(); + context_.Reset(); +} + +int WorkerInspectorClient::InspectorMessagesCallback(int fd, int events, void* data) { + // Clear the eventfd counter first or the level-triggered looper spins. + uint64_t value; + read(fd, &value, sizeof(value)); + + static_cast(data)->DrainIncoming(); + return 1; +} + +void WorkerInspectorClient::PushMessage(const std::string& message) { + if (dying_) { + return; + } + + { + std::lock_guard lock(incomingMutex_); + incoming_.push(message); + } + + if (eventFd_ != -1) { + // Wakes an idle worker pumping its looper. While paused the looper is + // not pumping — the condition variable below wakes the nested pause + // loop instead, and the still-readable fd fires one (possibly empty) + // drain after resume. + uint64_t value = 1; + write(eventFd_, &value, sizeof(value)); + } + messageArrived_.notify_all(); +} + +std::string WorkerInspectorClient::PopMessage() { + std::lock_guard lock(incomingMutex_); + if (incoming_.empty()) { + return ""; + } + std::string message = incoming_.front(); + incoming_.pop(); + return message; +} + +void WorkerInspectorClient::DrainIncoming() { + std::string message; + while (!dying_ && !(message = this->PopMessage()).empty()) { + this->DispatchOne(message); + } + this->MaybeResetSession(); +} + +void WorkerInspectorClient::DispatchOne(const std::string& message) { + if (message == kResetSessionMessage) { + this->HandleResetRequest(); + return; + } + + if (session_ == nullptr) { + return; + } + + v8::Locker locker(isolate_); + Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope(isolate_); + session_->dispatchProtocolMessage(Make8BitStringView(message)); + isolate_->PerformMicrotaskCheckpoint(); +} + +void WorkerInspectorClient::HandleResetRequest() { + if (runningPauseLoop_.load(std::memory_order_acquire)) { + // We're inside session_->dispatchProtocolMessage somewhere up the stack — + // resume now (which exits the nested pause loop) and swap the session + // only once that stack has fully unwound, from DrainIncoming. + pendingReset_ = true; + { + v8::Locker locker(isolate_); + Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope(isolate_); + if (session_ != nullptr) { + session_->resume(); + } + } + if (eventFd_ != -1) { + uint64_t value = 1; + write(eventFd_, &value, sizeof(value)); + } + return; + } + + this->DoResetSession(); +} + +void WorkerInspectorClient::DoResetSession() { + v8::Locker locker(isolate_); + Isolate::Scope isolate_scope(isolate_); + HandleScope handle_scope(isolate_); + if (session_ != nullptr) { + session_->resume(); + session_.reset(); + } + session_ = inspector_->connect(contextGroupId, this, {}); +} + +void WorkerInspectorClient::MaybeResetSession() { + if (pendingReset_ && !runningPauseLoop_.load(std::memory_order_acquire) && !dying_) { + pendingReset_ = false; + this->DoResetSession(); + } +} + +void WorkerInspectorClient::runMessageLoopOnPause(int contextGroupId) { + if (runningPauseLoop_.load(std::memory_order_acquire) || dying_) { + return; + } + runningPauseLoop_.store(true, std::memory_order_release); + pauseTerminated_ = false; + + while (!pauseTerminated_ && !dying_) { + std::string message = this->PopMessage(); + bool shouldWait = message.empty(); + if (!shouldWait) { + this->DispatchOne(message); + } + + while (v8::platform::PumpMessageLoop(Runtime::platform, isolate_)) { + } + + if (shouldWait && !pauseTerminated_ && !dying_) { + std::unique_lock lock(messageArrivedMutex_); + messageArrived_.wait_for(lock, std::chrono::milliseconds(1)); + } + } + + runningPauseLoop_.store(false, std::memory_order_release); +} + +void WorkerInspectorClient::quitMessageLoopOnPause() { + pauseTerminated_ = true; +} + +void WorkerInspectorClient::NotifyTerminating() { + dying_ = true; + pauseTerminated_ = true; + messageArrived_.notify_all(); +} + +void WorkerInspectorClient::RequestPauseInterrupt() { + isolate_->RequestInterrupt( + [](Isolate* isolate, void* data) { + // Runs on the worker thread mid-JS. Teardown also happens on the + // worker thread, so the client either still exists or this resolves + // to nothing — re-resolve through the registry instead of capturing + // the pointer. + int workerId = static_cast(reinterpret_cast(data)); + JsV8InspectorClient* root = JsV8InspectorClient::GetInstanceIfCreated(); + if (root != nullptr) { + root->SchedulePauseInWorker(workerId); + } + }, + reinterpret_cast(static_cast(workerId_))); +} + +void WorkerInspectorClient::SchedulePauseFromInterrupt() { + if (session_ != nullptr) { + session_->schedulePauseOnNextStatement({}, {}); + } +} + +void WorkerInspectorClient::sendResponse(int callId, std::unique_ptr message) { + this->SendWrapped(ToUtf8String(message->string())); +} + +void WorkerInspectorClient::sendNotification(std::unique_ptr message) { + this->SendWrapped(ToUtf8String(message->string())); +} + +void WorkerInspectorClient::flushProtocolNotifications() { +} + +void WorkerInspectorClient::SendWrapped(const std::string& message) { + if (message.empty() || message[0] != '{') { + return; + } + + // Flat-session protocol: tag everything this session emits with its + // sessionId so the frontend routes it to the right child target. + std::string wrapped; + wrapped.reserve(message.size() + sessionId_.size() + 16); + wrapped += "{\"sessionId\":\""; + wrapped += sessionId_; + wrapped += "\""; + if (message.size() > 2) { + wrapped += ","; + } + wrapped.append(message, 1, std::string::npos); + + JsV8InspectorClient* root = JsV8InspectorClient::GetInstanceIfCreated(); + if (root != nullptr) { + root->SendToFrontend(wrapped); + } +} + +void WorkerInspectorClient::consoleLog(ConsoleAPIType method, + const std::vector>& args) { + if (inspector_ == nullptr) { + return; + } + + // Note, here we access private V8 API (mirrors + // JsV8InspectorClient::consoleLogCallback). + auto* impl = reinterpret_cast(inspector_.get()); + + Local stack = + StackTrace::CurrentStackTrace(isolate_, 1, StackTrace::StackTraceOptions::kDetailed); + std::unique_ptr stackImpl = impl->debugger()->createStackTrace(stack); + + Local context = context_.Get(isolate_); + const int contextId = V8ContextInfo::executionContextId(context); + + std::unique_ptr msg = V8ConsoleMessage::createForConsoleAPI( + context, contextId, contextGroupId, impl, this->currentTimeMS(), method, args, String16{}, + std::move(stackImpl)); + + // Going through the message storage both reports to the session when the + // frontend has enabled the Runtime agent AND keeps the message for replay + // on Runtime.enable. Workers log most of their output (module top-level, + // early onmessage work) before DevTools attaches and enables the session; + // delivering straight to the runtime agent would silently drop all of it. + impl->ensureConsoleMessageStorage(contextGroupId)->addMessage(std::move(msg)); +} + +Local WorkerInspectorClient::ensureDefaultContextInGroup(int contextGroupId) { + return context_.Get(isolate_); +} + +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/WorkerInspectorClient.h b/test-app/runtime/src/main/cpp/WorkerInspectorClient.h new file mode 100644 index 000000000..a3deb572a --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerInspectorClient.h @@ -0,0 +1,135 @@ +#ifndef WORKERINSPECTORCLIENT_H_ +#define WORKERINSPECTORCLIENT_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "v8.h" +#include "v8-inspector.h" + +namespace tns { + +/* + * V8 inspector for a single worker isolate, exposed to Chrome DevTools as a + * child target ("Target.attachedToTarget") and addressed with flat-session + * CDP messages (a top-level "sessionId" field). One instance per worker, + * mirroring the iOS runtime's WorkerInspectorClient. + * + * Threading: constructed, dispatched into, and destroyed on the worker's own + * thread (V8's inspector is not thread-safe). Other threads interact only + * through PushMessage/NotifyTerminating/RequestPauseInterrupt. Incoming + * messages are queued and drained via an eventfd registered on the worker's + * ALooper (the same mechanism as the worker's message inbox); while paused, a + * nested loop on the worker thread pumps the same queue (the looper is NOT + * re-entered, so postMessage deliveries stay queued during a pause, matching + * Chrome's semantics). + */ +class WorkerInspectorClient final : public v8_inspector::V8InspectorClient, + public v8_inspector::V8Inspector::Channel { + public: + // Worker thread, with the worker isolate locked and its context created. + WorkerInspectorClient(int workerId, v8::Isolate* isolate, ALooper* workerLooper, + const std::string& url); + ~WorkerInspectorClient() override; + + int WorkerId() const { + return workerId_; + } + const std::string& SessionId() const { + return sessionId_; + } + const std::string& TargetId() const { + return targetId_; + } + const std::string& Url() const { + return url_; + } + + // Any thread. Queues a CDP message (already stripped of routing concerns) + // and wakes the worker looper / a nested pause loop. + void PushMessage(const std::string& message); + + // Any thread. Unblocks a paused worker and makes it drop all inspector + // work; used by WorkerWrapper::Terminate together with TerminateExecution. + void NotifyTerminating(); + + // Any thread (with the worker isolate guaranteed alive). Schedules a pause + // at the next statement even if the worker is busy executing JS. + void RequestPauseInterrupt(); + + // Worker thread (from the interrupt requested above). + void SchedulePauseFromInterrupt(); + + // Any thread. True while the worker sits in its nested pause loop — used + // by the root client to skip the pause interrupt (it would only fire + // after resume, causing a spurious re-pause). + bool IsRunningPauseLoop() const { + return runningPauseLoop_.load(std::memory_order_acquire); + } + + // Worker thread. Mirrors JsV8InspectorClient::consoleLogCallback for this + // isolate. + void consoleLog(v8_inspector::ConsoleAPIType method, + const std::vector>& args); + + // Internal control message pushed by the root client on frontend + // reconnect; resumes the worker if paused and recreates its session. + static constexpr const char* kResetSessionMessage = "{\"__nsInternal\":\"resetSession\"}"; + + // Overrides of V8Inspector::Channel + void sendResponse(int callId, std::unique_ptr message) override; + void sendNotification(std::unique_ptr message) override; + void flushProtocolNotifications() override; + + // Overrides of V8InspectorClient + void runMessageLoopOnPause(int contextGroupId) override; + void quitMessageLoopOnPause() override; + + private: + static constexpr int contextGroupId = 1; + + static int InspectorMessagesCallback(int fd, int events, void* data); + + void DrainIncoming(); + std::string PopMessage(); + void DispatchOne(const std::string& message); + void HandleResetRequest(); + void DoResetSession(); + void MaybeResetSession(); + void SendWrapped(const std::string& message); + + v8::Local ensureDefaultContextInGroup(int contextGroupId) override; + + int workerId_; + std::string sessionId_; + std::string targetId_; + std::string url_; + v8::Isolate* isolate_; + ALooper* workerLooper_; + int eventFd_ = -1; + + std::unique_ptr inspector_; + std::unique_ptr session_; + v8::Persistent context_; + + std::queue incoming_; + std::mutex incomingMutex_; + std::mutex messageArrivedMutex_; + std::condition_variable messageArrived_; + + std::atomic dying_{false}; + std::atomic pauseTerminated_{false}; + std::atomic runningPauseLoop_{false}; // written on the worker thread only + bool pendingReset_ = false; // worker thread only +}; + +} // namespace tns + +#endif /* WORKERINSPECTORCLIENT_H_ */ diff --git a/test-app/runtime/src/main/cpp/WorkerMessage.cpp b/test-app/runtime/src/main/cpp/WorkerMessage.cpp new file mode 100644 index 000000000..a2387328c --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerMessage.cpp @@ -0,0 +1,160 @@ +#include "WorkerMessage.h" + +#include + +#include "ArgConverter.h" + +using namespace v8; + +namespace tns { +namespace worker { +namespace { + +void ThrowDataCloneException(Local context, Local message) { + Isolate* isolate = context->GetIsolate(); + std::string msg = "DataCloneError: " + ArgConverter::ConvertToString(message); + isolate->ThrowException( + Exception::Error(ArgConverter::ConvertToV8String(isolate, msg))); +} + +class SerializerDelegate : public ValueSerializer::Delegate { +public: + SerializerDelegate(Isolate* isolate, Local context, Message* m) + : isolate_(isolate), context_(context), msg_(m) {} + + void ThrowDataCloneError(Local message) override { + ThrowDataCloneException(context_, message); + } + + Maybe WriteHostObject(Isolate* isolate, Local object) override { + // Host objects (e.g. Java proxies) carry no transferable native state; + // they are recreated as plain objects on the receiving side. + return Just(true); + } + + Maybe GetSharedArrayBufferId( + Isolate* isolate, Local shared_array_buffer) override { + uint32_t i; + for (i = 0; i < seen_shared_array_buffers_.size(); ++i) { + if (seen_shared_array_buffers_[i].Get(isolate) == shared_array_buffer) { + return Just(i); + } + } + + seen_shared_array_buffers_.emplace_back( + Global{isolate, shared_array_buffer}); + msg_->AddSharedArrayBuffer(shared_array_buffer->GetBackingStore()); + return Just(i); + } + + ValueSerializer* serializer = nullptr; + +private: + Isolate* isolate_; + Local context_; + Message* msg_; + std::vector> seen_shared_array_buffers_; + + friend class tns::worker::Message; +}; + +class DeserializerDelegate : public ValueDeserializer::Delegate { +public: + DeserializerDelegate( + Message* m, Isolate* isolate, + const std::vector>& shared_array_buffers) + : shared_array_buffers_(shared_array_buffers) {} + + MaybeLocal ReadHostObject(Isolate* isolate) override { + EscapableHandleScope scope(isolate); + Local object = Object::New(isolate); + return scope.Escape(object).As(); + } + + MaybeLocal GetSharedArrayBufferFromId( + Isolate* isolate, uint32_t clone_id) override { + if (clone_id >= shared_array_buffers_.size()) { + return MaybeLocal(); + } + return shared_array_buffers_[clone_id]; + } + + ValueDeserializer* deserializer = nullptr; + +private: + const std::vector>& shared_array_buffers_; +}; + +} // namespace + +Maybe Message::Serialize(Isolate* isolate, Local context, + Local input) { + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + + // Verify that we're not silently overwriting an existing message. + assert(main_message_buf_.is_empty()); + + SerializerDelegate delegate(isolate, context, this); + ValueSerializer serializer(isolate, &delegate); + delegate.serializer = &serializer; + + serializer.WriteHeader(); + if (serializer.WriteValue(context, input).IsNothing()) { + return Nothing(); + } + + // The serializer gave us a buffer allocated using `malloc()`. + std::pair data = serializer.Release(); + assert(data.first != nullptr); + main_message_buf_ = + MallocedBuffer(reinterpret_cast(data.first), data.second); + return Just(true); +} + +MaybeLocal Message::Deserialize(Isolate* isolate, Local context) { + Context::Scope context_scope(context); + EscapableHandleScope handle_scope(isolate); + + // Attach all transferred SharedArrayBuffers to their new Isolate. + std::vector> shared_array_buffers; + for (uint32_t i = 0; i < shared_array_buffers_.size(); ++i) { + Local sab = + SharedArrayBuffer::New(isolate, shared_array_buffers_[i]); + shared_array_buffers.push_back(sab); + } + + DeserializerDelegate delegate(this, isolate, shared_array_buffers); + ValueDeserializer deserializer( + isolate, reinterpret_cast(main_message_buf_.data), + main_message_buf_.size, &delegate); + delegate.deserializer = &deserializer; + + // Attach all transferred ArrayBuffers to their new Isolate. + for (uint32_t i = 0; i < array_buffers_.size(); ++i) { + Local ab = + ArrayBuffer::New(isolate, std::move(array_buffers_[i])); + deserializer.TransferArrayBuffer(i, ab); + } + + if (deserializer.ReadHeader(context).IsNothing()) { + return MaybeLocal(); + } + + Local return_value; + if (!deserializer.ReadValue(context).ToLocal(&return_value)) { + return MaybeLocal(); + } + + return handle_scope.Escape(return_value); +} + +void Message::AddSharedArrayBuffer(std::shared_ptr backing_store) { + shared_array_buffers_.emplace_back(std::move(backing_store)); +} + +Message::Message(MallocedBuffer&& payload) + : main_message_buf_(std::move(payload)) {} + +} // namespace worker +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/WorkerMessage.h b/test-app/runtime/src/main/cpp/WorkerMessage.h new file mode 100644 index 000000000..734fbad35 --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerMessage.h @@ -0,0 +1,103 @@ +#ifndef WORKERMESSAGE_H_ +#define WORKERMESSAGE_H_ + +#include +#include +#include +#include "v8.h" + +namespace tns { + +template +inline T* Malloc(size_t n) { + // n is an element count (see UncheckedRealloc), not a byte count + return static_cast(malloc(sizeof(T) * n)); +} + +template +T* UncheckedRealloc(T* pointer, size_t n) { + size_t full_size = sizeof(T) * n; + + if (full_size == 0) { + free(pointer); + return nullptr; + } + + void* allocated = realloc(pointer, full_size); + + return static_cast(allocated); +} + +template +struct MallocedBuffer { + T* data; + size_t size; + + T* release() { + T* ret = data; + data = nullptr; + return ret; + } + + void Truncate(size_t new_size) { + size = new_size; + } + + void Realloc(size_t new_size) { + Truncate(new_size); + data = UncheckedRealloc(data, new_size); + } + + bool is_empty() const { return data == nullptr; } + + MallocedBuffer() : data(nullptr), size(0) {} + explicit MallocedBuffer(size_t size) : data(Malloc(size)), size(size) {} + MallocedBuffer(T* data, size_t size) : data(data), size(size) {} + MallocedBuffer(MallocedBuffer&& other) : data(other.data), size(other.size) { + other.data = nullptr; + } + MallocedBuffer& operator=(MallocedBuffer&& other) { + this->~MallocedBuffer(); + return *new (this) MallocedBuffer(std::move(other)); + } + ~MallocedBuffer() { free(data); } + MallocedBuffer(const MallocedBuffer&) = delete; + MallocedBuffer& operator=(const MallocedBuffer&) = delete; +}; + +namespace worker { + +/* + * A structured-clone payload that can cross isolate/thread boundaries. + * Serialized with v8::ValueSerializer on the sending isolate and + * deserialized with v8::ValueDeserializer on the receiving one. + * SharedArrayBuffer contents are shared via their backing stores. + */ +class Message { + public: + Message(MallocedBuffer&& payload = MallocedBuffer()); + Message(Message&& other) = default; + Message& operator=(Message&& other) = default; + Message& operator=(const Message&) = delete; + Message(const Message&) = delete; + + v8::Maybe Serialize(v8::Isolate* isolate, + v8::Local context, + v8::Local input); + v8::MaybeLocal Deserialize(v8::Isolate* isolate, + v8::Local context); + + // Called when a new SharedArrayBuffer object is encountered in the + // incoming value's structure. + void AddSharedArrayBuffer(std::shared_ptr backing_store); + + private: + MallocedBuffer main_message_buf_; + std::vector> array_buffers_; + std::vector> shared_array_buffers_; +}; + +} // namespace worker +} // namespace tns + +#endif /* WORKERMESSAGE_H_ */ diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp new file mode 100644 index 000000000..d23c3d30e --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp @@ -0,0 +1,655 @@ +#include "WorkerWrapper.h" + +#include +#include + +#include + +#include "ArgConverter.h" +#include "CallbackHandlers.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +#ifdef APPLICATION_IN_DEBUG +#include "JsV8InspectorClient.h" +#include "WorkerInspectorClient.h" +#endif + +using namespace v8; + +namespace tns { + +WorkerWrapper::WorkerWrapper(Isolate* parentIsolate, int workerId, std::string workerPath, + std::string callingDir, int priority, + Local workerObject) + : parentIsolate_(parentIsolate), + // runs on the parent's thread, where the parent runtime is alive + parentTasks_(Runtime::GetRuntime(parentIsolate)->GetLooperTasks()), + workerIsolate_(nullptr), + runtime_(nullptr), + workerId_(workerId), + workerPath_(std::move(workerPath)), + callingDir_(std::move(callingDir)), + // workerPath_ (not workerPath) - the parameter was just moved from + threadName_("W" + std::to_string(workerId) + ": " + workerPath_), + priority_(priority), + poWorker_(new Persistent(parentIsolate, workerObject)), + isClosing_(false), + isTerminating_(false), + isDisposed_(false), + javaLooperRef_(nullptr) {} + +void WorkerWrapper::Start() { + auto self = shared_from_this(); + std::thread thread([self]() { + self->BackgroundLooper(self); + }); + thread.detach(); +} + +void WorkerWrapper::PostMessage(std::shared_ptr message) { + if (!isTerminating_ && !isClosing_) { + queue_.Push(message); + } +} + +void WorkerWrapper::PostMessageToParent(std::shared_ptr message) { + if (isTerminating_) { + return; + } + + auto parentTasks = parentTasks_.lock(); + if (parentTasks == nullptr) { + // the parent runtime is gone (e.g. a parent worker that shut down) + return; + } + + int workerId = workerId_; + parentTasks->Post([workerId, message]() { + WorkerWrapper::FireMessageOnParentWorkerObject(workerId, message); + }); +} + +void WorkerWrapper::Terminate() { + if (isClosing_ || isDisposed_) { + // The worker is already shutting down on its own; nothing to do. + return; + } + + bool wasTerminating = isTerminating_.exchange(true); + if (wasTerminating) { + return; + } + + Isolate* isolate = workerIsolate_.load(); + if (isolate != nullptr) { + // The only v8 call that is legal from another thread - interrupts any + // JS currently running on the worker (e.g. a busy loop). + isolate->TerminateExecution(); + } + +#ifdef APPLICATION_IN_DEBUG + { + // A worker paused at a breakpoint sits in the inspector's nested pause + // loop, not in Looper.loop() - kick it loose so TerminateExecution and + // the looper quit below can take effect. + std::lock_guard lock(inspectorMutex_); + if (inspector_ != nullptr) { + inspector_->NotifyTerminating(); + } + } +#endif + + QuitLooper(); +} + +void WorkerWrapper::Close() { + bool wasClosing = isClosing_.exchange(true); + if (wasClosing) { + return; + } + + // Once the current callback unwinds, Looper.loop() returns and the + // thread proceeds to cleanup. Pending messages are dropped, matching the + // previous front-of-queue TerminateAndCloseThread behavior. + QuitLooper(); +} + +void WorkerWrapper::QuitLooper() { + std::lock_guard lock(looperMutex_); + if (javaLooperRef_ != nullptr) { + JEnv env; + env.CallVoidMethod(javaLooperRef_, LOOPER_QUIT_METHOD_ID); + } +} + +int WorkerWrapper::DrainCallback(int fd, int events, void* data) { + uint64_t value; + read(fd, &value, sizeof(value)); + + auto wrapper = static_cast(data); + wrapper->DrainPendingTasks(); + return 1; +} + +void WorkerWrapper::DrainPendingTasks() { + Isolate* isolate = workerIsolate_.load(); + if (isolate == nullptr || isTerminating_) { + return; + } + + auto messages = queue_.PopAll(); + if (messages.empty()) { + return; + } + + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + auto context = runtime_->GetContext(); + Context::Scope context_scope(context); + auto globalObject = context->Global(); + + for (auto& message : messages) { + if (isTerminating_ || isClosing_) { + break; + } + + TryCatch tc(isolate); + + Local callback; + if (!globalObject->Get(context, ArgConverter::ConvertToV8String(isolate, "onmessage")) + .ToLocal(&callback) || + !callback->IsFunction()) { + DEBUG_WRITE( + "WORKER: couldn't fire a worker's `onmessage` callback because it isn't implemented!"); + continue; + } + + Local data; + if (message->Deserialize(isolate, context).ToLocal(&data)) { + auto event = Object::New(isolate); + event->DefineOwnProperty(context, ArgConverter::ConvertToV8String(isolate, "data"), + data, PropertyAttribute::ReadOnly); + Local args[] = {event}; + callback.As()->Call(context, Undefined(isolate), 1, args); + } + + if (tc.HasCaught() && !isTerminating_) { + CallbackHandlers::CallWorkerScopeOnErrorHandle(isolate, tc); + } + } +} + +void WorkerWrapper::FireMessageOnParentWorkerObject(int workerId, + std::shared_ptr message) { + auto wrapper = WorkerWrapper::GetById(workerId); + if (wrapper == nullptr) { + DEBUG_WRITE("MAIN: no worker instance was found with workerId=%d.", workerId); + return; + } + + Isolate* isolate = wrapper->parentIsolate_; + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + + if (wrapper->poWorker_ == nullptr || wrapper->poWorker_->IsEmpty()) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onmessage` callback because the worker has been cleared.", + workerId); + return; + } + + auto worker = Local::New(isolate, *wrapper->poWorker_); + auto context = Runtime::GetRuntime(isolate)->GetContext(); + Context::Scope context_scope(context); + + try { + Local callback; + if (!worker->Get(context, ArgConverter::ConvertToV8String(isolate, "onmessage")) + .ToLocal(&callback) || + !callback->IsFunction()) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onmessage` callback because it isn't implemented.", + workerId); + return; + } + + Local data; + if (message->Deserialize(isolate, context).ToLocal(&data)) { + auto event = Object::New(isolate); + event->DefineOwnProperty(context, ArgConverter::ConvertToV8String(isolate, "data"), + data, PropertyAttribute::ReadOnly); + Local args[] = {event}; + callback.As()->Call(context, Undefined(isolate), 1, args); + } + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(); + } +} + +void WorkerWrapper::PassUncaughtExceptionFromWorkerToParent(const std::string& message, + const std::string& filename, + const std::string& stackTrace, + int lineno) { + auto parentTasks = parentTasks_.lock(); + if (parentTasks == nullptr) { + // the parent runtime is gone (e.g. a parent worker that shut down) + return; + } + + int workerId = workerId_; + std::string threadName = threadName_; + Isolate* parentIsolate = parentIsolate_; + + parentTasks->Post([workerId, message, filename, stackTrace, lineno, threadName, + parentIsolate]() { + v8::Locker locker(parentIsolate); + Isolate::Scope isolate_scope(parentIsolate); + HandleScope handle_scope(parentIsolate); + auto context = Runtime::GetRuntime(parentIsolate)->GetContext(); + Context::Scope context_scope(context); + + WorkerWrapper::FireErrorOnParentWorkerObject(workerId, message, stackTrace, filename, + lineno, threadName); + }); +} + +void WorkerWrapper::FireErrorOnParentWorkerObject(int workerId, const std::string& message, + const std::string& stackTrace, + const std::string& filename, int lineno, + const std::string& threadName) { + auto wrapper = WorkerWrapper::GetById(workerId); + if (wrapper == nullptr) { + DEBUG_WRITE("MAIN: no worker instance was found with workerId=%d.", workerId); + return; + } + + Isolate* isolate = wrapper->parentIsolate_; + + try { + if (wrapper->poWorker_ == nullptr || wrapper->poWorker_->IsEmpty()) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onerror` callback because the worker has been cleared.", + workerId); + return; + } + + auto worker = Local::New(isolate, *wrapper->poWorker_); + auto context = Runtime::GetRuntime(isolate)->GetContext(); + + Local callback; + bool hasOnError = + worker->Get(context, ArgConverter::ConvertToV8String(isolate, "onerror")) + .ToLocal(&callback) && + callback->IsFunction(); + + if (hasOnError) { + auto errEvent = Object::New(isolate); + errEvent->Set(context, ArgConverter::ConvertToV8String(isolate, "message"), + ArgConverter::ConvertToV8String(isolate, message)); + errEvent->Set(context, ArgConverter::ConvertToV8String(isolate, "stackTrace"), + ArgConverter::ConvertToV8String(isolate, stackTrace)); + errEvent->Set(context, ArgConverter::ConvertToV8String(isolate, "filename"), + ArgConverter::ConvertToV8String(isolate, filename)); + errEvent->Set(context, ArgConverter::ConvertToV8String(isolate, "lineno"), + Number::New(isolate, lineno)); + + Local args[] = {errEvent}; + + // If the handler returns a truthy value the exception is handled + // and must not be raised to application level + Local result; + callback.As()->Call(context, Undefined(isolate), 1, args).ToLocal(&result); + if (!result.IsEmpty() && result->BooleanValue(isolate)) { + return; + } + } + + DEBUG_WRITE( + "Unhandled exception in '%s' thread. file: %s, line %d, message: %s\nStackTrace: %s", + threadName.c_str(), filename.c_str(), lineno, message.c_str(), + stackTrace.c_str()); + } catch (NativeScriptException& ex) { + ex.ReThrowToV8(); + } +} + +void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { + JavaVM* jvm = Runtime::GetJVM(); + JNIEnv* jniEnv = nullptr; + + JavaVMAttachArgs attachArgs; + attachArgs.version = JNI_VERSION_1_6; + attachArgs.name = const_cast(threadName_.c_str()); + attachArgs.group = nullptr; + jvm->AttachCurrentThread(&jniEnv, &attachArgs); + + // pthread names are limited to 15 chars + pthread_setname_np(pthread_self(), threadName_.substr(0, 15).c_str()); + + int runtimeId = -1; + + try { + JEnv env; + + // Performs the cgroup/scheduling-policy move in addition to the nice + // value, exactly like the previous Java-side + // Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND) call. + env.CallStaticVoidMethod(PROCESS_CLASS, SET_THREAD_PRIORITY_METHOD_ID, priority_); + + if (!isTerminating_ && !isClosing_) { + // Prepares the Java Looper for this thread and creates the + // per-worker com.tns.Runtime (which creates the worker isolate on + // this thread via initNativeScript -> PrepareV8Runtime). + JniLocalRef callingDir(env.NewStringUTF(callingDir_.c_str())); + runtimeId = env.CallStaticIntMethod(RUNTIME_CLASS, INIT_WORKER_RUNTIME_METHOD_ID, + workerId_, (jstring) callingDir); + runtime_ = Runtime::GetRuntime(runtimeId); + + { + std::lock_guard lock(looperMutex_); + JniLocalRef looper(env.CallStaticObjectMethod(LOOPER_CLASS, MY_LOOPER_METHOD_ID)); + javaLooperRef_ = env.NewGlobalRef(looper); + } + + // Looper.prepare() ran above, so ALooper_forThread() returns the + // native looper backing the Java one - fds added here are pumped + // by Looper.loop(). + queue_.Initialize(ALooper_forThread(), WorkerWrapper::DrainCallback, this); + + Isolate* isolate = runtime_->GetIsolate(); + + { + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + isolate->SetData((uint32_t) Runtime::IsolateData::WORKER_WRAPPER, this); + workerIsolate_.store(isolate); + + auto context = runtime_->GetContext(); + Context::Scope context_scope(context); + +#ifdef APPLICATION_IN_DEBUG + // Expose this worker to an attached Chrome DevTools frontend + // as a child target, mirroring the iOS runtime. Created before + // the script runs so the worker's scripts are visible to the + // debugger from the start. + CreateInspector(isolate); +#endif + + if (!isTerminating_) { + runtime_->RunWorker(workerPath_); + } + } + + // Deliver messages that were posted before the worker was ready + // (replaces the old Java Handshake + pendingWorkerMessages). + DrainPendingTasks(); + + if (!isTerminating_ && !isClosing_) { + // Blocks, pumping Java Handler messages (cross-thread Java->JS + // calls), timers and the worker inbox until quit() is called. + env.CallStaticVoidMethod(RUNTIME_CLASS, RUN_WORKER_LOOP_METHOD_ID); + } + } + } catch (NativeScriptException& ex) { + if (jniEnv->ExceptionCheck()) { + jniEnv->ExceptionClear(); + } + if (!isTerminating_) { + PassUncaughtExceptionFromWorkerToParent(ex.GetErrorMessage(), workerPath_, "", 0); + } + } catch (std::exception& ex) { + DEBUG_WRITE_FORCE("Worker(id=%d) error: c++ exception: %s", workerId_, ex.what()); + } catch (...) { + DEBUG_WRITE_FORCE("Worker(id=%d) error: unknown c++ exception!", workerId_); + } + + // ----- Shutdown (close, terminate or bootstrap failure) ----- + + isTerminating_ = true; + + // Terminate any workers this worker created (nested workers). Their + // Worker object persistents live in this isolate, so they must be + // released before the isolate is disposed below. Each child cascades to + // its own children during its shutdown. + { + Isolate* isolate = workerIsolate_.load(); + if (isolate != nullptr) { + TerminateChildren(isolate); + } + } + +#ifdef APPLICATION_IN_DEBUG + // The inspector must be gone before the Runtime (and with it the isolate) + // is deleted below; unregistering also tells DevTools the target is gone. + DestroyInspector(); +#endif + + // On this thread: safe to unregister the inbox fd from the looper. + queue_.Terminate(); + + if (runtime_ != nullptr) { + try { + // Java-side detach (GcListener.unsubscribe + runtimeCache.remove) + // must happen before the isolate is disposed, preserving the old + // WorkerThreadHandler -> TerminateWorkerCallback ordering. + JEnv env; + env.CallStaticVoidMethod(RUNTIME_CLASS, DETACH_WORKER_RUNTIME_METHOD_ID, runtimeId); + } catch (NativeScriptException& ex) { + if (jniEnv->ExceptionCheck()) { + jniEnv->ExceptionClear(); + } + DEBUG_WRITE_FORCE("Worker(id=%d) error while detaching Java runtime: %s", workerId_, + ex.GetErrorMessage().c_str()); + } + + // Take the isolate from the runtime, not from workerIsolate_: if the + // bootstrap failed between initWorkerRuntime and the workerIsolate_ + // publish (e.g. a JNI error while resolving the looper), the atomic is + // still null while the isolate very much needs disposing. + workerIsolate_.store(nullptr); + Isolate* isolate = runtime_->GetIsolate(); + { + v8::Locker locker(isolate); + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + runtime_->DestroyRuntime(); + } + isolate->Dispose(); + + // The Runtime destructor still makes JNI calls - it must run before + // DetachCurrentThread below. + delete runtime_; + runtime_ = nullptr; + } + + { + std::lock_guard lock(looperMutex_); + if (javaLooperRef_ != nullptr) { + jniEnv->DeleteGlobalRef(javaLooperRef_); + javaLooperRef_ = nullptr; + } + } + + isDisposed_ = true; + + // Notify the parent thread so the Worker object's persistent handle and + // the registry entry are released (no-op if terminate() or the parent's + // own shutdown already cleared them). + if (auto parentTasks = parentTasks_.lock()) { + int workerId = workerId_; + parentTasks->Post([workerId]() { + WorkerWrapper::ClearWorkerOnParent(workerId); + }); + } + + // ART aborts if a native thread exits while still attached. This must be + // the very last JNI-touching action on this thread. + jvm->DetachCurrentThread(); +} + +int WorkerWrapper::NextWorkerId() { + return nextWorkerId_.fetch_add(1, std::memory_order_relaxed) + 1; +} + +std::shared_ptr WorkerWrapper::GetById(int workerId) { + std::lock_guard lock(registryMutex_); + auto it = registry_.find(workerId); + return it != registry_.end() ? it->second : nullptr; +} + +void WorkerWrapper::Insert(int workerId, std::shared_ptr wrapper) { + std::lock_guard lock(registryMutex_); + registry_.emplace(workerId, std::move(wrapper)); +} + +void WorkerWrapper::ClearWorkerOnParent(int workerId) { + std::shared_ptr wrapper; + { + std::lock_guard lock(registryMutex_); + auto it = registry_.find(workerId); + if (it == registry_.end()) { + return; + } + wrapper = it->second; + registry_.erase(it); + } + + if (wrapper->poWorker_ != nullptr) { + v8::Locker locker(wrapper->parentIsolate_); + wrapper->poWorker_->Reset(); + delete wrapper->poWorker_; + wrapper->poWorker_ = nullptr; + } +} + +void WorkerWrapper::TerminateChildren(Isolate* parentIsolate) { + std::vector> children; + { + std::lock_guard lock(registryMutex_); + for (auto& entry : registry_) { + if (entry.second->parentIsolate_ == parentIsolate) { + children.push_back(entry.second); + } + } + } + + for (auto& child : children) { + DEBUG_WRITE( + "Terminating nested worker(id=%d) because its parent is shutting down", + child->workerId_); + child->Terminate(); + ClearWorkerOnParent(child->workerId_); + } +} + +WorkerWrapper* WorkerWrapper::FromIsolate(Isolate* isolate) { + return static_cast( + isolate->GetData((uint32_t) Runtime::IsolateData::WORKER_WRAPPER)); +} + +void WorkerWrapper::EnsureJniCached() { + if (RUNTIME_CLASS != nullptr) { + return; + } + + JEnv env; + + RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + INIT_WORKER_RUNTIME_METHOD_ID = + env.GetStaticMethodID(RUNTIME_CLASS, "initWorkerRuntime", "(ILjava/lang/String;)I"); + RUN_WORKER_LOOP_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "runWorkerLoop", "()V"); + DETACH_WORKER_RUNTIME_METHOD_ID = + env.GetStaticMethodID(RUNTIME_CLASS, "detachWorkerRuntime", "(I)V"); + + LOOPER_CLASS = env.FindClass("android/os/Looper"); + assert(LOOPER_CLASS != nullptr); + MY_LOOPER_METHOD_ID = + env.GetStaticMethodID(LOOPER_CLASS, "myLooper", "()Landroid/os/Looper;"); + LOOPER_QUIT_METHOD_ID = env.GetMethodID(LOOPER_CLASS, "quit", "()V"); + + PROCESS_CLASS = env.FindClass("android/os/Process"); + assert(PROCESS_CLASS != nullptr); + SET_THREAD_PRIORITY_METHOD_ID = + env.GetStaticMethodID(PROCESS_CLASS, "setThreadPriority", "(I)V"); +} + +#ifdef APPLICATION_IN_DEBUG +void WorkerWrapper::CreateInspector(Isolate* isolate) { + // Only when the root inspector client exists (debuggable app, created on + // the main thread during runtime init) - never construct it from here. + JsV8InspectorClient* root = JsV8InspectorClient::GetInstanceIfCreated(); + if (root == nullptr) { + return; + } + + // Same url scheme the module loader reports in Debugger.scriptParsed. + // workerPath_ may still be relative to the caller's dir at this point + // (resolution happens in require); callingDir_ ends with '/'. + std::string url = + "file://" + (workerPath_[0] == '/' ? workerPath_ : callingDir_ + workerPath_); + + auto* client = new WorkerInspectorClient(workerId_, isolate, ALooper_forThread(), url); + { + std::lock_guard lock(inspectorMutex_); + inspector_ = client; + } + + // Register only once fully constructed: registration makes the client + // reachable from the socket thread. + root->RegisterWorkerTarget(workerId_, client); +} + +void WorkerWrapper::DestroyInspector() { + WorkerInspectorClient* client = nullptr; + { + std::lock_guard lock(inspectorMutex_); + client = inspector_; + inspector_ = nullptr; + } + + if (client == nullptr) { + return; + } + + // Unregister first: after this returns no other thread can reach the + // client through the root's registry. + JsV8InspectorClient* root = JsV8InspectorClient::GetInstanceIfCreated(); + if (root != nullptr) { + root->UnregisterWorkerTarget(workerId_); + } + + delete client; +} + +void WorkerWrapper::ConsoleLog(v8_inspector::ConsoleAPIType method, + const std::vector>& args) { + std::lock_guard lock(inspectorMutex_); + if (inspector_ != nullptr) { + inspector_->consoleLog(method, args); + } +} +#endif + +std::mutex WorkerWrapper::registryMutex_; +std::map> WorkerWrapper::registry_; +std::atomic_int WorkerWrapper::nextWorkerId_(0); + +jclass WorkerWrapper::RUNTIME_CLASS = nullptr; +jclass WorkerWrapper::LOOPER_CLASS = nullptr; +jclass WorkerWrapper::PROCESS_CLASS = nullptr; +jmethodID WorkerWrapper::INIT_WORKER_RUNTIME_METHOD_ID = nullptr; +jmethodID WorkerWrapper::RUN_WORKER_LOOP_METHOD_ID = nullptr; +jmethodID WorkerWrapper::DETACH_WORKER_RUNTIME_METHOD_ID = nullptr; +jmethodID WorkerWrapper::MY_LOOPER_METHOD_ID = nullptr; +jmethodID WorkerWrapper::LOOPER_QUIT_METHOD_ID = nullptr; +jmethodID WorkerWrapper::SET_THREAD_PRIORITY_METHOD_ID = nullptr; + +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.h b/test-app/runtime/src/main/cpp/WorkerWrapper.h new file mode 100644 index 000000000..464145d2f --- /dev/null +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.h @@ -0,0 +1,208 @@ +#ifndef WORKERWRAPPER_H_ +#define WORKERWRAPPER_H_ + +#include + +#include +#include +#include +#include +#include + +#ifdef APPLICATION_IN_DEBUG +#include + +#include +#endif + +#include "ConcurrentQueue.h" +#include "WorkerMessage.h" +#include "v8.h" + +namespace tns { + +class LooperTasks; +class Runtime; +#ifdef APPLICATION_IN_DEBUG +class WorkerInspectorClient; +#endif + +/* + * Owns a worker's native thread and its lifecycle, mirroring the iOS + * runtime's WorkerWrapper. The thread is a std::thread that attaches to the + * JVM, prepares a Java Looper (so worker/plugin code can keep using Android + * Handlers) and then drives that single looper, which pumps both Java + * messages and the worker's C++ inbox (ConcurrentQueue + eventfd). + * + * Messaging is done entirely in C++ with V8 ValueSerializer payloads: + * - parent -> worker: queue_ + eventfd wakeup on the worker looper + * - worker -> parent: the parent runtime's LooperTasks queue + * + * The parent may be the main thread or another worker (nested workers); a + * worker's children are terminated when the worker itself shuts down. + */ +class WorkerWrapper : public std::enable_shared_from_this { +public: + WorkerWrapper(v8::Isolate* parentIsolate, int workerId, std::string workerPath, + std::string callingDir, int priority, + v8::Local workerObject); + + int WorkerId() const { return workerId_; } + bool IsTerminating() const { return isTerminating_; } + bool IsClosing() const { return isClosing_; } + bool IsDisposed() const { return isDisposed_; } + + /* + * Spawns the (detached) worker thread. The thread holds a shared_ptr to + * this wrapper, keeping it alive until the thread fully shuts down. + */ + void Start(); + + /* + * parent -> worker. Queues a serialized message and wakes the worker + * looper. Messages posted before the worker finishes bootstrapping are + * drained right after the worker script runs. + */ + void PostMessage(std::shared_ptr message); + + /* + * worker -> parent. Posts a task that fires the Worker object's + * `onmessage` on the parent runtime's thread. + */ + void PostMessageToParent(std::shared_ptr message); + + /* + * Called on the parent's thread. Interrupts running JS (TerminateExecution + * is the one v8 call that is legal cross-thread) and quits the worker + * looper. + */ + void Terminate(); + + /* + * Called on the worker thread (self.close()). Lets the current callback + * unwind, then the looper quits and the thread shuts down gracefully. + */ + void Close(); + + /* + * Posts the worker object's `onerror` invocation to the parent's thread. + * Strings only - must not hold any v8 handles from the worker isolate. + */ + void PassUncaughtExceptionFromWorkerToParent(const std::string& message, + const std::string& filename, + const std::string& stackTrace, + int lineno); + + /* + * Registry of live workers, keyed by workerId. Replaces the old + * CallbackHandlers::id2WorkerMap. Guarded by a mutex because the worker + * shutdown path posts cleanup from the worker thread. + */ + static int NextWorkerId(); + static std::shared_ptr GetById(int workerId); + static void Insert(int workerId, std::shared_ptr wrapper); + + /* + * Parent thread only: resets the Worker object's persistent handle and + * removes the wrapper from the registry. Idempotent. + */ + static void ClearWorkerOnParent(int workerId); + + /* + * Terminates and clears all workers whose parent is the given isolate. + * Must run on the parent's thread, before the parent isolate is disposed + * (the children's Worker object persistents live in that isolate). + * Cascades: each child terminates its own children during shutdown. + */ + static void TerminateChildren(v8::Isolate* parentIsolate); + + /* + * Resolves the wrapper of a worker isolate via its isolate data slot. + * Returns nullptr on the main isolate. + */ + static WorkerWrapper* FromIsolate(v8::Isolate* isolate); + + /* + * Resolves the jclass/jmethodID handles used by the worker thread + * bootstrap, before the first worker starts. The first call is always on + * the main thread (nested workers require a main-thread worker first), + * where class loading is safe. + */ + static void EnsureJniCached(); + +#ifdef APPLICATION_IN_DEBUG + /* + * Worker thread (console.* fires on the isolate's own thread). Forwards + * to the worker's inspector so the log reaches DevTools' worker target. + */ + void ConsoleLog(v8_inspector::ConsoleAPIType method, + const std::vector>& args); +#endif + +private: + void BackgroundLooper(std::shared_ptr self); + void DrainPendingTasks(); + void QuitLooper(); + static int DrainCallback(int fd, int events, void* data); + static void FireMessageOnParentWorkerObject(int workerId, + std::shared_ptr message); + static void FireErrorOnParentWorkerObject(int workerId, const std::string& message, + const std::string& stackTrace, + const std::string& filename, int lineno, + const std::string& threadName); + + v8::Isolate* parentIsolate_; + // The parent runtime's task queue; weak so a child outliving its parent + // just drops its posts instead of touching a dead runtime. + std::weak_ptr parentTasks_; + std::atomic workerIsolate_; + Runtime* runtime_; + + const int workerId_; + const std::string workerPath_; + const std::string callingDir_; + const std::string threadName_; + const int priority_; + + v8::Persistent* poWorker_; + + std::atomic_bool isClosing_; + std::atomic_bool isTerminating_; + std::atomic_bool isDisposed_; + + ConcurrentQueue queue_; + + std::mutex looperMutex_; + jobject javaLooperRef_; + +#ifdef APPLICATION_IN_DEBUG + /* + * Expose this worker to an attached Chrome DevTools frontend as a child + * target. Created on the worker thread before the script runs, destroyed + * on the worker thread before the isolate is disposed. + */ + void CreateInspector(v8::Isolate* isolate); + void DestroyInspector(); + + WorkerInspectorClient* inspector_ = nullptr; + std::mutex inspectorMutex_; +#endif + + static std::mutex registryMutex_; + static std::map> registry_; + static std::atomic_int nextWorkerId_; + + static jclass RUNTIME_CLASS; + static jclass LOOPER_CLASS; + static jclass PROCESS_CLASS; + static jmethodID INIT_WORKER_RUNTIME_METHOD_ID; + static jmethodID RUN_WORKER_LOOP_METHOD_ID; + static jmethodID DETACH_WORKER_RUNTIME_METHOD_ID; + static jmethodID MY_LOOPER_METHOD_ID; + static jmethodID LOOPER_QUIT_METHOD_ID; + static jmethodID SET_THREAD_PRIORITY_METHOD_ID; +}; + +} // namespace tns + +#endif /* WORKERWRAPPER_H_ */ diff --git a/test-app/runtime/src/main/cpp/com_tns_AndroidJsV8Inspector.cpp b/test-app/runtime/src/main/cpp/com_tns_AndroidJsV8Inspector.cpp index cdef52e62..090a8db80 100644 --- a/test-app/runtime/src/main/cpp/com_tns_AndroidJsV8Inspector.cpp +++ b/test-app/runtime/src/main/cpp/com_tns_AndroidJsV8Inspector.cpp @@ -28,3 +28,19 @@ JNIEXPORT extern "C" void Java_com_tns_AndroidJsV8Inspector_dispatchMessage(JNIE std::string message = ArgConverter::jstringToString(jMessage); JsV8InspectorClient::GetInstance()->dispatchMessage(message); } + +JNIEXPORT extern "C" jstring Java_com_tns_AndroidJsV8Inspector_handleMessageOnSocketThread(JNIEnv* env, jobject instance, jstring jMessage) { + try { + std::string message = ArgConverter::jstringToString(jMessage); + std::string response; + if (JsV8InspectorClient::GetInstance()->handleMessageOnSocketThread(message, response)) { + // Handled; an empty string means any replies were already sent. + return env->NewStringUTF(response.c_str()); + } + } catch (...) { + // must never propagate a native exception into the websocket thread + } + + // Not handled -> Java queues it to the main-thread dispatcher. + return nullptr; +} diff --git a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp index a05d869fb..5b6981156 100644 --- a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp +++ b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp @@ -153,34 +153,6 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_runModule(JNIEnv* _env, jobject o } } -extern "C" JNIEXPORT void Java_com_tns_Runtime_runWorker(JNIEnv* _env, jobject obj, jint runtimeId, jstring scriptFile) { - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - return; - } - - auto isolate = runtime->GetIsolate(); - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - auto context = runtime->GetContext(); - v8::Context::Scope context_scope(context); - - try { - runtime->RunWorker(scriptFile); - } catch (NativeScriptException& e) { - e.ReThrowToJava(); - } catch (std::exception e) { - stringstream ss; - ss << "Error: c++ exception: " << e.what() << endl; - NativeScriptException nsEx(ss.str()); - nsEx.ReThrowToJava(); - } catch (...) { - NativeScriptException nsEx(std::string("Error: c++ exception!")); - nsEx.ReThrowToJava(); - } -} - extern "C" JNIEXPORT jobject Java_com_tns_Runtime_runScript(JNIEnv* _env, jobject obj, jint runtimeId, jstring scriptFile) { jobject result = nullptr; @@ -380,105 +352,6 @@ extern "C" JNIEXPORT jint Java_com_tns_Runtime_getCurrentRuntimeIdLegacy(JNIEnv* return getCurrentRuntimeIdCritical_impl(); } -extern "C" JNIEXPORT void Java_com_tns_Runtime_WorkerGlobalOnMessageCallback(JNIEnv* env, jobject obj, jint runtimeId, jstring msg) { - // Worker Thread runtime - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - // TODO: Pete: Log message informing the developer of the failure - } - - auto isolate = runtime->GetIsolate(); - - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - auto context = runtime->GetContext(); - v8::Context::Scope context_scope(context); - - CallbackHandlers::WorkerGlobalOnMessageCallback(isolate, msg); -} - -extern "C" JNIEXPORT void Java_com_tns_Runtime_WorkerObjectOnMessageCallback(JNIEnv* env, jobject obj, jint runtimeId, jint workerId, jstring msg) { - // Main Thread runtime - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - // TODO: Pete: Log message informing the developer of the failure - } - - auto isolate = runtime->GetIsolate(); - - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - auto context = runtime->GetContext(); - v8::Context::Scope context_scope(context); - - CallbackHandlers::WorkerObjectOnMessageCallback(isolate, workerId, msg); -} - -extern "C" JNIEXPORT void Java_com_tns_Runtime_TerminateWorkerCallback(JNIEnv* env, jobject obj, jint runtimeId) { - // Worker Thread runtime - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - // TODO: Pete: Log message informing the developer of the failure - } - - auto isolate = runtime->GetIsolate(); - - { - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - - CallbackHandlers::TerminateWorkerThread(isolate); - - runtime->DestroyRuntime(); - } - - isolate->Dispose(); - - delete runtime; -} - -extern "C" JNIEXPORT void Java_com_tns_Runtime_ClearWorkerPersistent(JNIEnv* env, jobject obj, jint runtimeId, jint workerId) { - // Worker Thread runtime - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - // TODO: Pete: Log message informing the developer of the failure - } - - auto isolate = runtime->GetIsolate(); - - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - auto context = runtime->GetContext(); - v8::Context::Scope context_scope(context); - - CallbackHandlers::ClearWorkerPersistent(workerId); -} - -extern "C" JNIEXPORT void Java_com_tns_Runtime_CallWorkerObjectOnErrorHandleMain(JNIEnv* env, jobject obj, jint runtimeId, jint workerId, jstring message, jstring stackTrace, jstring filename, jint lineno, jstring threadName) { - // Main Thread runtime - auto runtime = TryGetRuntime(runtimeId); - if (runtime == nullptr) { - // TODO: Pete: Log message informing the developer of the failure - } - - auto isolate = runtime->GetIsolate(); - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - auto context = runtime->GetContext(); - v8::Context::Scope context_scope(context); - - try { - CallbackHandlers::CallWorkerObjectOnErrorHandle(isolate, workerId, message, stackTrace, filename, lineno, threadName); - } catch (NativeScriptException& e) { - e.ReThrowToJava(); - } -} - extern "C" JNIEXPORT void Java_com_tns_Runtime_ResetDateTimeConfigurationCache(JNIEnv* _env, jobject obj, jint runtimeId) { auto runtime = TryGetRuntime(runtimeId); if (runtime == nullptr) { diff --git a/test-app/runtime/src/main/cpp/conversions/objects/JSToJavaObjectsConverter.cpp b/test-app/runtime/src/main/cpp/conversions/objects/JSToJavaObjectsConverter.cpp index e92bb9e26..d9ad333d2 100644 --- a/test-app/runtime/src/main/cpp/conversions/objects/JSToJavaObjectsConverter.cpp +++ b/test-app/runtime/src/main/cpp/conversions/objects/JSToJavaObjectsConverter.cpp @@ -118,7 +118,8 @@ bool tns::ConvertJavaScriptObject( obj = objectManager->GetJavaObjectByJsObject(jsObject); if (obj.IsNull() && (jsObject->IsTypedArray() || jsObject->IsArrayBuffer() || - jsObject->IsArrayBufferView())) { + jsObject->IsArrayBufferView() || + jsObject->IsSharedArrayBuffer())) { JavaObjectCastType bufferCastType = tns::JavaObjectCastType::Byte; std::shared_ptr store; @@ -130,6 +131,10 @@ bool tns::ConvertJavaScriptObject( auto array = jsObject.As(); store = array->GetBackingStore(); length = array->ByteLength(); + } else if (jsObject->IsSharedArrayBuffer()) { + auto array = jsObject.As(); + store = array->GetBackingStore(); + length = array->ByteLength(); } else if (jsObject->IsArrayBufferView()) { auto array = jsObject.As(); diff --git a/test-app/runtime/src/main/cpp/third_party/json.hpp b/test-app/runtime/src/main/cpp/third_party/json.hpp new file mode 100644 index 000000000..cd4924d96 --- /dev/null +++ b/test-app/runtime/src/main/cpp/third_party/json.hpp @@ -0,0 +1,25722 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used C++ version, this is skipped +#if !defined(JSON_HAS_CPP_26) && !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202302L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202302L) + #define JSON_HAS_CPP_26 + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has a syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow accessing some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows calling any std function as if (e.g., with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find the first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find the next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +inline void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // char_traits +#include // tuple +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L + #include // byte +#endif +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non-CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e., those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g., to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for the pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; + +#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L +template<> +struct char_traits : std::char_traits +{ + using char_type = std::byte; + using int_type = uint64_t; + + static int_type to_int_type(char_type c) noexcept + { + return static_cast(std::to_integer(c)); + } + + static char_type to_char_type(int_type i) noexcept + { + return std::byte(static_cast(i)); + } + + static constexpr int_type eof() noexcept + { + return static_cast(std::char_traits::eof()); + } +}; +#endif + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g., Clang 3.5 or GCC 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to use it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& +is_complete_type < +detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template